← All writing
Craft · · 6 min

Three themes deep

On template fallback, the override that's really a fork, and why the file you're editing is a position in a lookup order rather than a thing you own.

Ecommerce PHP

I spent most of Tuesday trying to find where a price gets rendered on a product page. I found four files that could plausibly have been it. The one actually running was in none of the places I looked first.

This is not a complaint about a bad codebase. The codebase is fine. It’s that I arrived from somewhere with one templates folder, where finding the file that draws a thing is a question you answer with a search, and I hadn’t understood that here the file is not a thing you find. It’s a thing the platform resolves, at runtime, out of an ordered list of places it’s willing to look.

Ask and ye shall receive

The shape is simple once somebody says it out loud, which nobody had.

There’s a base set of templates that ships with the platform. Above it sits a package, and above that a theme, and when the code asks for catalog/product/price it walks down that stack asking each level whether it has one, and takes the first answer. Your theme wins if it has an opinion. If it doesn’t, the question falls through to the level below, and eventually to base, which always has one.

Which means an override is not a modification. You never edit the original. You put a file with the same path higher in the stack, and the original is still there, untouched, being ignored.

The reason for this is entirely defensible and it’s the same reason WordPress has child themes: the platform needs to be able to ship an update without destroying your work, and the only way to guarantee that is to make sure your work never touched its files in the first place.

An override isn't a modification. The original is still there, untouched, being ignored, which is the whole point and also the whole problem.

Fork in the road

Here’s what nobody tells you, and it took me a week and a bad merge to work it out.

To change one line, you copy the whole file.

There is no mechanism for saying “this template, but with line 34 different.” You take the 200 lines the platform shipped, put them in your theme, change your one line, and now you own all 200. The other 199 are a snapshot of what the platform looked like on the day you copied it.

Then the platform updates. Base gets a fix: an escaping bug, a new attribute, a markup change for some feature you don’t use yet. Every site that didn’t override that template picks the fix up for free.

You don’t. Your copy is higher in the stack, so it wins, and it wins with the 2014 version of a file that got fixed in 2016. Nothing errors. Nothing warns you. The site works, and it works slightly wrong, in a way that will surface in about eight months as “the price shows without tax on one page.”

Override responsibly

Three rules, all learned the tedious way.

Override the smallest thing that will hold. If the platform gives you a hook, a layout instruction, an event, anything that lets you change behavior without copying markup, take it even when it’s uglier than the copy. A layout XML instruction that moves a block is four lines that survive an upgrade. A copied template is 200 lines that don’t.

Write down what you changed and why, in the file. Not in a wiki. At the top of the template, three lines: what version this came from, what I altered, and what it would take to stop needing the override. When the next person diffs it, that comment is the difference between ten minutes and an afternoon.

Check whether the thing already falls through. About a third of the overrides I inherited on this project change nothing. Somebody copied a file, intended to edit it, got interrupted, and the copy stayed, identical to base, sitting in the theme, freezing that template at an old version for no benefit whatsoever. Those are free deletions and there are always some.

Indirection isn’t overengineering

For about a month I treated the fallback as an obstacle. I kept wanting a flat templates directory, and I’d catch myself thinking the indirection was overengineering by people who liked systems more than they liked shipping.

Then we took an upgrade, and the sites where somebody had been disciplined about overriding narrowly took an afternoon, and the one where a previous developer had copied the entire theme wholesale took most of two weeks. Same platform, same version jump. The difference was entirely how much of the base they’d forked without noticing they were forking it.

So the indirection isn’t overengineering. It’s the platform trying to keep a boundary between its code and mine, and every time I copy a file I am the one choosing to erase that boundary. It just doesn’t feel like a choice at the time, because at the time it’s a 200-line file and one line I need different.

Answers, not files

The reframe that helped: stop thinking of a template as a file and start thinking of it as an answer to a question the platform is asking. You’re not editing the site. You’re adding an entry to a lookup, and the entry you add outranks everything below it forever, including the parts you never meant to have an opinion about.

Which is a lot of ceremony for changing where a price sits. But I’ve now watched the alternative, and the alternative is a codebase where nobody can take an update.

Read similar posts
7 min

The checkout isn't yours

Shopify and Magento aren't really competing on features, they're competing on how much of your business logic you're allowed to have, and the answer depends less on your catalog than on whether you have engineers.

6 min

The replatform nobody calls a replatform

Moving a store from a server we controlled to one we don't was sold internally as an infrastructure decision, and it is not an infrastructure decision, it's a decision about which problems you're allowed to have.