← all writing

Nº02 // WRITING

One engine, three plugins

There’s a piece of code I’ve now shipped three times without really rewriting it. It’s a small conditional-logic engine: give it a set of rules and a bag of current values, and it tells you which fields are visible, which are required, and what they cost. It started in my WooCommerce product-options plugin. Then it became the whole spine of my checkout-fields plugin. Then it turned up again, nearly untouched, inside my cost calculator. Three products, three different problems, one engine.

That reuse is deliberate, and it’s the part of my work I’d most want another developer to copy. Not the engine itself — the discipline around it.

The engine doesn’t know what plugin it’s in

The trick that makes the engine reusable is that it knows almost nothing. It doesn’t know what a product is, or a checkout, or a quote. It takes two things: a list of rules, and a flat map of values — { field_a: "yes", field_b: 3 } — and it answers questions about them. Is this rule satisfied? Given everything that’s true right now, which fields are active?

Everything plugin-specific happens before the engine, in the step that builds that values map. In product options, the values are the choices a customer made on the product page. In checkout fields, they’re a mix of form inputs and context — the cart’s contents, the customer’s role, the chosen payment method — flattened into the same shape, so a rule can say “show this field only for wholesale customers paying by bank transfer” using the exact same machinery as “show this field when the engraving checkbox is ticked.” In the calculator, the values are the inputs to a quote.

Three completely different domains, collapsed into one boring question: given these values, which rules pass? The engine answers that and nothing else. Because it’s that small and that ignorant, dropping it into a new plugin is almost free — I write the part that builds the values, and the hard logic just comes along for the ride.

The catch: it has to run twice

Here’s where it gets interesting, and where most of the real work went.

A conditional-logic engine like this has to run on the server. That’s not optional. The browser can’t be trusted with anything that matters: a “required” field that a rule has hidden must not block the order, and a priced add-on that’s hidden must not be charged. If the only copy of the logic lives in JavaScript, a user with the dev tools open can hand you whatever they like. So the source of truth — the version that decides what’s required and what gets charged — runs in PHP, on submit, where the customer can’t reach it.

But the customer also needs the form to feel alive. When they tick a box and a new field should appear, or a price should change, that has to happen instantly, in the browser, without a round-trip. Which means the same logic also has to run in JavaScript.

So now I have the same engine implemented twice, in two languages — and that’s a quietly dangerous thing to own. Two copies of any logic drift. Someone fixes an edge case in the PHP, forgets the JS, and now the form shows one thing and the server does another. For a plugin that decides what a customer is charged, “the preview disagreed with the invoice” isn’t a cosmetic bug. It’s the worst kind.

Shared fixtures, so the two can’t disagree

The fix is almost embarrassingly simple, and it’s the single best decision in the whole design: the two implementations are tested against the same file.

The rules and their expected outcomes live in a plain JSON file — a list of cases, each one a set of rules, a set of values, and the answer the engine should give. The PHP test suite loads that file and runs every case. The JavaScript test suite loads the same file and runs every case. Neither language owns the truth; the fixture does. The day the PHP and JS implementations disagree about any case in that file, one of the two suites goes red, and I find out at my desk instead of from a customer.

The calculator pushed this further, because it does money math, and money math is where floating point quietly betrays you. 0.1 + 0.2 is not 0.3, and a calculator that’s a cent off on a big quote is a calculator nobody trusts. So its formula engine doesn’t use floats at all — it works in fixed-point integers under the hood, mirrored in PHP and JS, with its own shared fixture file of worked examples. Same principle, higher stakes: write the cases once, hold both languages to them.

What this actually buys me

People assume the value of code reuse is typing less. It isn’t, really — the engine is small; I could rewrite it in an afternoon. The value is trust. Every case in those fixture files is a small fight I already had and won. When I start a new plugin, I don’t re-fight them. I inherit a conditional-logic engine that’s been proven against dozens of cases in two languages, and I get to spend my attention on the part that’s actually new.

It also means a bug fixed once is fixed everywhere. When I find an edge case in how an OR-group of conditions resolves, I add a case to the fixture, fix it until both suites pass, and every plugin gets the fix the next time I pull the engine forward. That’s the opposite of the usual plugin-sprawl story, where the same bug lives in five slightly different copies, you fix it four times, and miss the fifth.

The boring lesson

None of this is clever. There’s no trick. It’s two unglamorous rules I follow every time: keep the engine ignorant of the plugin around it, and never let two copies of the same logic exist without a shared test that forces them to agree. Do that, and “I’ll just reuse it” stops being a lie developers tell themselves and becomes something you can actually bank on.

The engine is the asset. The fixtures are what make it one.