"Cannot use object of type Product as array": PrestaShop product pages return 500 at random

by Luca Cavallini Support cases
"Cannot use object of type Product as array": PrestaShop product pages return 500 at random

The symptom: real customers hitting an error page, at random

A PrestaShop 1.7 store with a custom theme was logging around seventy 500 errors a day, roughly one every 15 minutes, all on product pages and on add-to-cart requests. Not bots: Italian IP addresses, ordinary browsers (Safari, Chrome). Every one of those errors is a real customer who was looking at a product, or about to buy it, and got a blank page instead.

The log always showed the same fatal error:

PHP Fatal error: Uncaught Error:
  Cannot use object of type Product as array
  in .../product-flags.tpl.php
  on line 44

The offending line, in the file Smarty had compiled, mapped back to $product.flags in the source template product-flags.tpl, the partial that draws the "on sale", "bundle", "new" badges under the product name. It didn't happen on every product page, and not always on the same products.

What it looked like, and wasn't

Before reaching the real cause, three reasonable suspects were checked and ruled out one at a time.

  • A module hooked into the product page. The natural suspicion, with a custom theme, is that some third-party module is overwriting the product variable passed to Smarty. Every module hooked into the relevant hooks (chat, reviews, related-products blocks, tracking) was checked one by one: none assigned product or products in a way that could explain the error.
  • The store's custom product controller override. The store had a product page controller override, written years earlier. Checked line by line: it only touches the category assignment, not the variable that triggers the error.
  • "These are products with corrupted data." The affected products, checked individually, held ordinary data: nothing broken in the fields, nothing missing. The trait they shared, once looked at together, was having at least one active badge (a promotion, a bundle), not a malformed value.

None of the three explained why the error was intermittent: it happened sometimes on one product and not on another that was structurally identical, and on the same product it sometimes showed up and sometimes didn't.

The real cause (partial, and it needs to be said honestly)

In PrestaShop, the $product variable that reaches templates is normally a ProductLazyArray: a class that implements ArrayAccess, so $product['flags'] in PHP and $product.flags in Smarty both work because the object knows how to behave like an array even though it isn't one. In some contexts, though, a raw Product object shows up instead, the actual ObjectModel, which does not implement ArrayAccess. Accessing a field on an object like that with array syntax doesn't raise a recoverable notice: it throws a fatal Error, uncatchable by PrestaShop, which stops execution and returns a 500 to the visitor.

It needs to be said with the same honesty it was treated with on the actual store: exactly who passes the raw Product object instead of the ProductLazyArray was never pinned down in the time available. The most obvious hooks were ruled out, but the precise injection point is still to be isolated, most likely somewhere less visible in the theme's code or in a specific rendering context. That's also why the fix below was applied deliberately as a mitigation, not as a root-cause fix.

The fix: make the template tolerant, not guess who's at fault

When the exact cause can't be isolated in the time available, and the effect is a site returning 500 to customers, the priority shifts: first remove the urgency (the 500 goes away), then, without the pressure, keep looking for whoever is injecting the wrong data. The template was changed to read the badges only when $product is genuinely accessible as an array, and to stay quiet (empty badges, not a broken page) when it isn't:

{assign var='flags'
  value=null}
{if isset($product) &&
    ($product|is_array ||
     $product|is_a:
       'ArrayAccess')}
    {assign var='flags'
      value=$product.flags}
{/if}
<ul class="product-flags">
  {if $flags}
    {foreach from=$flags
      item=flag}
      ...
    {/foreach}
  {/if}
</ul>

It covers both legitimate cases: a plain PHP array (is_array) and a ProductLazyArray-style object (is_a:'ArrayAccess'). When the raw object shows up, which is neither of the two, the block simply stays empty instead of taking the whole page down.

A check worth doing before, not after. Using a Smarty modifier like |is_array or |is_a assumes the theme allows calling PHP functions from templates. On many installs that's true by default, but not on all of them: if a Smarty security policy forbids it, the exact same change would break the compilation of every product page, not just the case it was meant to fix. Before writing the modifier, it's worth checking whether the theme already uses an equivalent one somewhere (in this case, |count already appeared seventeen times in the same theme): if it's already there, the path is clear.

How to verify it holds

  • Clear only the compiled file for that specific template, found with find inside var/cache/prod/smarty/compile/, never the whole cache folder: it's the only way to force that one file to recompile without paying the cost of rebuilding the store's entire Smarty cache.
  • Reload the product pages that used to fail and check two things at once: that they return 200, and that the badges (promotion, discount) are still visible where they're supposed to be. A template made "tolerant" the wrong way can stop throwing errors while also hiding the good data, not just the broken one.
  • Count the error's occurrences in the log before and after, don't stop at the first check: grep -c "Cannot use object of type Product as array" error.log a few hours later, not a few minutes later, because the error showed up at intervals of tens of minutes, and too short a test can look like a success by pure luck.
  • Keep a reminder for the root cause. The mitigation removes the urgency, it doesn't close the case: it's worth checking, later on, whether the same pattern ($something.key on a partial that expects an array but sometimes gets an object) repeats elsewhere in the same theme, because one faulty injection point can easily touch more than one template.

The store stays anonymous on purpose: the mechanism is what matters. If a PrestaShop theme throws Cannot use object of type Product as array on a partial reading $product.something, the right suspicion isn't "that product is broken": it's that at that specific point in rendering, sometimes, a real Product arrives instead of the ProductLazyArray the template expects.

Leave a comment

Comments are moderated before publication.

← Blog

Loading...
Back to top