How template fallback picks a file
The ticket was to make the currency symbol on product pages a size smaller than the number beside it. Wrap the $ in a span, one line of CSS, 10 minutes.
So I found the template that draws a price, added the span, saved, reloaded. Identical page. I cleared the cache, because this is Magento and clearing the cache is the answer to about a third of everything. Still identical.
Most of that day went to finding out why. There were four copies of that template sitting on disk, I edited two of the wrong ones before I got to the one the store was using, and the one the store was using is the one I’d have bet against.
None of this is a complaint about the codebase. The codebase is fine. I came from a place with one templates folder, where the file that draws a thing is a question you answer with ctrl+f. Six weeks into a shop that builds stores, and still new enough to assume the problem is me, I hadn’t understood that here the file isn’t a thing you find. It’s a thing the platform picks, at runtime, out of an ordered list of places it’s willing to look.
Duck, duck, base
The shape of it is simple, and nobody had ever said it out loud to me.
Magento comes with a full set of templates in base/default. Above that sits a package, and above the package sits your theme. When something asks for catalog/product/price.phtml the platform starts at the top of that stack and works down, taking the first copy it finds. Your theme wins if it has an opinion. If it doesn’t, the question falls through to the package default, and then to base, which always has one.
Here is what was on disk.
$ find app/design -name price.phtml
app/design/frontend/base/default/template/catalog/product/price.phtml
app/design/frontend/rwd/default/template/catalog/product/price.phtml
app/design/frontend/ourpackage/default/template/catalog/product/price.phtml
app/design/frontend/ourpackage/sale/template/catalog/product/price.phtml
One of those is never consulted at all. rwd is the responsive package that comes with 1.9, we aren’t using it, and its templates will never draw anything on this store. I edited that one first, naturally, on the grounds that it was the first path find handed back.
The two in the middle are real fallbacks and they both lose, because the theme at the top of the stack has a copy of its own. The theme at the top of the stack is sale, which somebody built for a promotion in December and which the store has been pointed at ever since. (The setting that decides this lives at store view scope, three screens into the admin, and is its own small essay!)
Spot the difference
So I opened the winner next to the file it was overriding, expecting to find whatever it is that makes a sale theme a sale theme.
It was the same file. Somebody had copied it over and changed nothing.
I wonder if they copied it meaning to change something, got pulled onto something else that afternoon, and never came back, because that is precisely the route I would have taken to the same place. Nothing in the file will tell you, which is part of what I want to write down here.
Then I diffed the rest of the theme against what it was overriding, which took 20 minutes and which I should have done in my first week. About 40 templates in that theme, and 13 of them are identical to the file underneath.
Everyone else got the fix
An override isn’t a modification. You never touch the original. You put a file with the same path higher in the stack and the original stays exactly where it was, untouched, being ignored.
The reasoning is the same reasoning behind WordPress child themes: the platform has to be able to release an update without destroying your work, and the only way to promise that is to make sure your work never touched its files in the first place.
Nobody had mentioned the price of the promise. 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 140 lines the platform gave you, put them in your theme, change your one line, and now you own all 140. The other 139 are a snapshot of what the platform looked like on the day you copied it.
It’s a photocopy that outranks the original. You take the page, correct one word on the copy, file the copy on top. It works beautifully right up until somebody reprints the original with a correction of their own. Your copy is still on top. Nothing about the cabinet looks wrong.
Which is where the 13 identical files stop being funny. Each one pins a template to whatever version it was copied at and buys nothing in return. One of them is a block a platform patch touched in June, so the store has been drawing it the December way for five months. Nothing errors, nothing warns you, the price on the page is correct, and the markup under it is a season behind. It’s a condition with no symptom, and my sense is that those are the ones that live longest.
An afternoon and two weeks
For my first few weeks I treated the fallback as an obstacle. I kept wanting a flat templates folder. I’d catch myself deciding the indirection was the work of people who like systems more than they like building things, which is a thought I have had about most things I didn’t understand yet.
Then I watched us take a version bump across two stores in the same month. The store where somebody had been careful about overriding narrowly took an afternoon. The other one, where a previous developer had copied the whole theme over all at once, is into its second week and a fair amount of swearing. Same platform, same version jump.
The difference is how much of the base each of them had forked without noticing that forking was the thing they were doing.
So the indirection isn’t overengineering. It’s the platform holding a line between its code and mine, and every time I copy a file I am the one deciding to erase that line. It doesn’t feel like a decision at the time. At the time it’s a 140-line file and one line I need different.
Copy that
Three things I do now, all of them learned the tedious way.
Override the smallest thing that will hold. If the platform offers a layout instruction, a block, an event, anything that changes behavior without copying markup, take it even when it’s uglier than the copy would have been. A few lines of layout XML that move a block survive an upgrade. A copied template is 140 lines that don’t.
Write down what you changed, in the file, not in a wiki. Three lines at the top saying which version this came from, what I altered, and what would have to be true for the next person to delete it. The next person diffing that file is the difference between 10 minutes and an afternoon, and often enough the next person is me.
And diff the theme against what it’s overriding, every so often. A third of mine were doing nothing whatsoever and I only know that because I finally looked. Those are free deletions, and there are always some.
Whose file is it
In the end it helped to stop thinking of a template as a file and start thinking of it as an answer to a question the platform keeps asking. I’m not editing the site. The file I’m editing is a position in a lookup order instead of a thing I own, and the position outranks everything below it forever, including all the parts of it I never meant to have an opinion about.
I wrote back in June about a hero image that grew to 2.4 MB months after I handed the site over, and about performance work having a shelf life I hadn’t thought about. This is that from the other end. The override is my decision, frozen on the day I made it, and the thing quietly moving on without it is the platform instead of the client.
Which is a lot of ceremony for making a dollar sign smaller. But I’ve now seen the other version, and the other version is a store nobody can update, arrived at one reasonable copy at a time by people who each only needed one line different. Nobody in that history did anything I wouldn’t have done in April.