How template fallback picks a file

#ecommerce #php

The ticket was to make the currency symbol on product prices a size smaller than the number beside it. Wrap the $ in a span, add a line of CSS, 10 minutes.

So I found the template that draws a price, added the span, saved, and reloaded. Identical page. I flushed the cache because this is Magento and flushing the cache is the answer to about a third of everything. Still identical.

Most of that day went to finding out why. There were five copies of that template on disk, and I edited two of the wrong ones before I got to the one the store was using. It was the one I’d have bet against. Then it turned out not to have the dollar sign in it.

None of this is a complaint about the codebase. The codebase is fine. I came from a place where a site had one theme folder and the file that draws a thing was a question you answered with ctrl+f.

Six weeks into a shop that builds stores, I’m 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.

Every template has a default copy inside the module it belongs to. Above that sit themes, and each theme can name a parent. Our theme names Luma, Luma names Blank, and Blank names nobody.

When something asks for Magento_Catalog::product/price/final_price.phtml, the platform starts at the store’s theme and works up through its parents. Then it falls back to the module’s own copies. The storefront’s comes first and then the one in base, which the storefront shares with the admin. It takes the first copy it finds.

Your theme wins if it has an opinion. If it doesn’t, the question falls through Luma and Blank (which almost never have one) to the module, which always does.

Here’s what was on disk.

$ find app/design vendor/magento -name final_price.phtml
app/design/frontend/Ourvendor/default/Magento_Catalog/templates/product/price/final_price.phtml
app/design/frontend/Ourvendor/sale/Magento_Catalog/templates/product/price/final_price.phtml
vendor/magento/module-bundle/view/base/templates/product/price/final_price.phtml
vendor/magento/module-catalog/view/base/templates/product/price/final_price.phtml
vendor/magento/module-grouped-product/view/base/templates/product/price/final_price.phtml
Shell

Two of those never draw anything on this store. Bundles and grouped products each get a price template of their own, and the store doesn’t sell either.

The one in the Catalog module is the default. Naturally I edited it first because the template’s name says Magento_Catalog and that’s where Magento_Catalog lives. It loses to both themes.

It’s also the one you never edit since vendor/ belongs to Composer and the next update writes straight over it. One of the back-end developers explained that very kindly once they’d stopped wincing.

The two in our themes are real overrides. The one in default loses too because the theme at the top of the stack has a copy of its own.

That theme is sale, a child of default that somebody built for a promotion in the spring. The store has been pointed at it ever since. (The setting that decides this lives under Content, then Design, then Configuration, at store view scope, 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. That’s precisely the route I would have taken to the same place. Nothing in the file will tell you.

Then I diffed the rest of both themes against what they override. It took 20 minutes and I should have done it in my first week. There are about 40 templates between them, and 13 are identical to the file underneath.

Follow the money

And the dollar sign isn’t in it.

final_price.phtml draws the box a price sits in and hands the number itself to another template, amount/default.phtml. That one doesn’t have the $ either. It calls a PHP method, formatCurrency(), which hands back the symbol and the number already joined into one string ($24.00) with nowhere between them to put a span.

So the seam I needed is in PHP, and I don’t write PHP. The 10-minute ticket ended with me writing a ticket for one of the back-end developers.

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.

WordPress child themes run on the same reasoning. The platform has to be able to release an update without destroying your work. 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’s no mechanism for saying “this template, but with line 34 different.”

You take the 64 lines the platform gave you, put them in your theme, change 1 of them, and now you own all 64. The other 63 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, and 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 and nothing about the cabinet looks wrong.

That’s 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 template the 2.1 release changed in June. The store upgraded over the summer and it’s been drawing that part the 2.0 way since. Nothing errors, nothing warns you, the page looks right, and the markup under it is a release behind. It’s a condition with no symptom and I think 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’ve had about most things I didn’t understand yet.

Then I watched us take the same patch release on two stores in the same month. The store where somebody had been careful about overriding narrowly took an afternoon. On the other, somebody before my time had copied every template they thought they might want into the theme on day one. That one is into its second week and a fair amount of swearing.

Same platform, same release. The difference is how much of the platform each of them had forked without noticing they were forking anything.

So I don’t think the indirection is overengineering. The platform is holding a line between its code and mine. Every time I copy a file I’m the one deciding to erase that line.

It doesn’t feel like a decision at the time. At the time it’s a 64-line file and 1 line I need different.

Copy that

I do three things now and I learned all of them the tedious way.

I override the smallest thing that will hold. If the platform offers a layout instruction, a block argument, a LESS variable, or anything else that changes the page without copying markup, I 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 64 lines that don’t.

I write down what I changed in the file itself, not in a wiki. Three lines at the top say which version this came from, what I altered, and what would have to be true for the next person to delete it. For the next person diffing that file it’s the difference between 10 minutes and an afternoon, and often enough the next person is me.

And every so often I diff the theme against what it’s overriding. A third of ours were doing nothing whatsoever and I only know that because I finally looked. Those are free deletions and I’d guess 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. 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’d gotten it down to 180 KB, 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. This time the platform is quietly moving on without it instead of the client.

Which is a lot of ceremony for making a dollar sign smaller, especially since I wasn’t the one who made it smaller in the end. But I’ve now seen the other version. It’s 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.

Read similar posts

View posts by tag

#accessibility #agency #ai #careers #cross-browser-compatibility #css #culture #debugging #documentation #ecommerce #forms #git #html #images #javascript #performance #php #privacy #scss #security #shopify #small-print #tooling #typography #wordpress