Issues with forms in acorn 4.x

Previously I could put code into a blade component’s class (the code that executes behind the actual blade template) and handle form POSTs, redirecting using wp_redirect when the post was successful. I used this to create some reusable “opt in” forms across various pages of the site.

In 4.x of acorn, calling wp_redirect just stays on the page but no error is thrown. I can add error_log calls up to the wp_redirect and they display, but the redirect never happens.

I posted an issue on github about the issue as well.

Is anyone else trying to redirect inside acorn 4 components? And if not, does anyone have suggestions for how to work around this?

I’m not sure why it worked for you in Acorn v3 and it’s not on v4, but handling a redirect from your component doesn’t seem like the appropriate place for that functionality

What about just moving that to a service provider instead?

Well I have a gutenberg block that uses wordpress’s server side rendering to render the blade template into any wordpress page that has the component on it. So it made sense to me (and it worked fine) to put the logic for handling posts to the component in its associated file.

Can you expand on why/how I’d do this with a service provider? I use service providers for a bunch of other stuff but not sure how you’re envisioning doing redirects there. Thank you.

Apologies for chipping in on this, but I have an idea.

I agree with @ben that doing request stuff in a view component constructor isn’t correct, but it is tricky when SSR-ing a Gutenberg block.

IMO, the most idiomatic way to handle a Gutenberg block redirect would be to register and hook the redirect into a query var (via service provider), then pass the appropriate query var via the form.

Alternatively, as a hack, you could stick with your current implementation and fire up output buffering early so that the headers never get sent until you’re ready.

add_action('muplugins_loaded', function() { ob_start(); });
1 Like

Am I the only one here that sees this as an even bigger hack? Doesn’t putting logic behind reusable components seem like a pretty basic first-class capability of laravel?

When this would work in a component, it was great because I could use laravel’s validation and provide nice validation errors on the page too. You can just check the request object passed into the constructor for whether it’s a post and access everything you need - laravel style. I can’t just redirect automatically when I see a querystring variable, I have to do form value validation first.

There’s got to be something better than burying it behind something watching for a querystring variable.

I don’t believe the “Laravel style” is to put this sort of functionality in your component files

I’m not understanding how it’s hacky to move your form redirect outside of the component

I guess I’m saying since we can’t use controllers to back our wordpress templates, we need somewhere to have business logic for reusable components. Since you have access to the request object in the constructor of the component it lets you know when there’s a form post. Seems like a good place to handle the post and do the redirect? The validators also work there. Not sure how to handle all that in a service provider. Service providers I thought were more for middleware and configuring wordpress, not UI logic?.

I could be just misunderstanding how you intend for us to implement form validation as well as a redirect within a service provider.

@talss89 can you help me understand why redirecting in components doesn’t work in v4? I’m not sure what ob_start firing during must use plugins loading is doing in your suggestion. That may help me figure out the best way to handle this.

Hi @eavonius, sure. I didn’t want to hijack the thread with a wordy explanation.

I haven’t reviewed the Acorn v4 codebase, so can’t point you to a commit / change that introduced this. However, the ob_start() workaround works as follows:

A HTTP response looks like this on the wire:

HTTP/1.1 200 OK\n
Content-Length: 11\n
Content-Type: text/plain; charset=UTF-8\n
Some-Header: foo\n
Some-Other-Header: bar\n
Hello World

A redirect requires a Location HTTP header to be sent, along with a 30x response code.

With no output buffering, headers are usually just transmitted straight to the browser (as there’s no benefit in holding them back). As soon as we start to write or echo any sort of response, the headers will have ‘left the building’.

class-wp-styles.php has already started outputting a response, so by the time your component constructor runs and attempts to redirect it’s impossible to go back and modify the headers and response code.

Starting output buffering with ob_start() early allows headers to be set but not sent over the socket to the browser. When we ob_start(), we’re telling PHP not to write to the socket and transmit anything over the wire, but to write into a buffer. The buffer is then ‘flushed’ or sent either upon termination, or with a variant of the ob_flush() functions.

With output buffering active multiple attempts at setting a header, even after a response has been echoed, will simply modify the header in the buffer. Once we finish execution, PHP flushes the buffer and sends to the web server, which sends to the client browser.

I used muplugins_loaded as the early hook to start output buffering from. That may be too early. wp_loaded might be a better shout.

Coming full circle and relating this back to Acorn - if v4 is in fact the culprit here (not FSE or Gutenberg or WP), then I suspect this issue stems from Acorn rendering views later on in execution than it did previously.

You hit on a really valid point.

You’re attempting to use a View (Component) to drive Controller logic. And the reason why you’re doing that makes total sense - you’re building a functional Gutenberg block which could be considered a Controller in it’s own right.

Out of the almost infinite number of approaches to this problem, I can see two stand-outs:

  1. Implement a Controller layer with a service provider (with WordPress query_vars, checking for form submission or another mechanism)
  2. Enabling output buffering as described above, giving you flexibility to put controller logic in a view.

@talss89 thank you so much for your detailed response. It really helped fill in the blanks for me.

The part I am still a little confused about is how to implement validation. I know I could create a service provider that hooks various wordpress actions to filter and then execute logic based on query variables. But I need a way to enforce validation of form fields, then communicate those back to the calling form if there’s a problem so they can be displayed properly.

In acorn 3, I could do this by using the Illuminate\Support\Facades\Validator facade acting upon an instance of the Illuminate\Http\Request that gets passed in as a constructor parameter to components. Then I could surface errors returned by the validator to the blade view itself to display them.

I’m not sure how to achieve the same from a provider. I guess this is what leads me to the confusion. One of the really awesome things about components (at least how acorn implemented them in v3) was that the request being passed in let me use other features of laravel. If I move processing posts to a provider, it doesn’t have the same context.

Just wanted to follow up that I did indeed end up going this route. It does feel kind of like a hack, but considering rendering server side templates from gutenberg blocks through blade probably isn’t common - I can understand it.

I don’t really know what the implications on other plugins are, but so far it seems to work fine in production.

I did notice that in newer versions of sage, there’s a view statement in the wordpress theme blade files that seems to use laravel views directly. When I started using sage 10, this wasn’t a pattern I saw present in the starter themes.

Maybe this would be something that would help with my situation? Does using the view statement somehow make other aspects of laravel’s view rendering pipeline (like validations and allowing redirects etc.) possible perhaps?

Thanks again for both y’alls support and workaround so far. Really appreciate it.