14. Storefront: shoppers with a cart
You will: keep shoppers on Flx after they add something to the cart, with nothing about them reaching the renderer.
Result: a cart holder's page is byte-identical to a no-cart visitor's, apart from a random id WooCommerce gives quantity fields.
The idea
Every visitor gets the same page, built with an empty cart. WooCommerce's cart fragments script then fills in the real cart in the browser, as it already does on cached pages.
The trade-off: on the first page in a browser tab, the header shows an empty cart until that request returns. Later pages fill it in as they load.
Hiding cookies isn't enough
By template_redirect, WooCommerce has already loaded the visitor's session, cart and customer. Hiding cookies at that point changes nothing.
Emptying the cart in place is dangerous: a crash or an exit in a hook leaves the visitor's real cart empty, and it is saved at shutdown. A new WC_Cart() isn't safe either: its constructor loads the session and adds a shutdown hook that clears the cart cookies when the cart is empty.
So the plugin swaps stand-ins in for the build, and never changes the real objects:
$store->session = new InertSession(); // Reads find nothing; writes stay in memory.
$store->cart = self::empty_copy( $cart ); // A clone, emptied.
$store->customer = new WC_Customer( 0, true );
The shutdown hooks stay bound to the real objects, so whatever happens during the build, the real cart is what gets saved. Everything is put back in a finally, and again at the very start of shutdown, since finally doesn't run after a fatal error or an exit.
Three details matter:
- Nonces. Guest WooCommerce nonces use the session's customer id, through a filter on the real session. It has to be taken out for the build, found by object rather than by method name, so a rename inside WooCommerce can't quietly put session-bound nonces on the page.
- Notices. A notice waiting in the session is printed by the page and then cleared. A page built with stand-ins would lose it, so a waiting notice sends that one page to native.
- The subclass.
InertSessionextends a WooCommerce class. If a WooCommerce update changes a method's signature, PHP stops with a fatal error that can't be caught. So the plugin compares signatures with reflection first, and if they don't match, the page is native.
Prove it
The check that matters is byte equality: fetch each page as a cart holder and as a visitor with no cart, and compare. It catches anything the code didn't think of. Add a probe that prints what hooks can see of the shopper:
<!-- flx-cart-probe: items=0 postcode= nonce=... -->
Then the controls: on a native page, the same probe must show the real cart, postcode and a different nonce. Without a control, the test could be passing because the probe doesn't work.
A failure that taught us something
One run lost a cart on a control page. WooCommerce answers an add-to-cart request before PHP saves the session at shutdown, so a request sent straight after can read the old session and save over the new one. It looks like the feature losing carts, and it happens with no Flx involved. The test now waits until the cart is saved, and checks the cart on a native page before and after the Flx pages, so the two cases can be told apart.
Next: 15. Sections in TypeScript.
Published from rickey29/twenty-twenty-five at commit 837705d · Flx Modernize 1.11.0 · Flx Render 1.11.0