How to Use PrestaShop’s Override System Without Breaking Core

PrestaShop’s override system allows you customize core behavior without touching original files. But used carelessly, it creates fragile stores that break on updates. This guide by Knowband covers how overrides work, when to use them, and three critical gotchas every developer should know.

What Is the Override System?

PrestaShop uses PHP inheritance to let you swap in your own version of any core class without touching the originals. Every core class is named with a Core suffix for example, ProductCore. If you create /override/classes/Product.php with a class called Product that extends ProductCore, PrestaShop loads your version instead. The same applies to controllers.

Only override the specific methods you need to change never copy an entire class file. This keeps your override lean and far less likely to conflict with future updates.

Example, exposing the current controller name to your header template:

Where to Put Your Override Files

Overrides can live in two places. The global /override/ directory at the root of your installation is active immediately after clearing the cache. The better approach is bundling overrides inside a module.

Placing an override inside a module (e.g. /modules/mymodule/override/classes/Product.php) causes PrestaShop to copy it to /override/ on installation and remove it cleanly on uninstall. This is the recommended approach.

A quick example of a class override. Say you want to expose the current controller name to your header template:

// /override/classes/controllers/FrontController.php

class FrontController extends FrontControllerCore {

    public function initHeader() {

        self::$smarty->assign('currentController', get_class($this));

        return parent::initHeader();

    } }

Always call the parent method unless you have a deliberate reason not to. Skipping it will silently break core functionality.

Overriding Module Classes and Controllers

Since PrestaShop 1.6, you can override module classes too. Append “Override” to the class name (e.g. BlockUserInfoOverride extending BlockUserInfo) and place it under /override/modules/. The same pattern applies to module controllers. After any override, clear the cache, specifically delete /cache/class_index.php, or it won’t take effect.

When NOT to Use Overrides

Overrides are exclusive — only one per class can exist. They’re also forbidden in marketplace modules and should be reserved for private store customisations. Before reaching for an override, consider one of these alternatives:

  • Hooks: PrestaShop triggers hook events across every page load — you can tap into them from a module to run custom code or push content into the page without going anywhere near a core class.
  • Symfony Service Overrides: Parts of PrestaShop that have been migrated to Symfony give you a cleaner path through the service container. You decorate or replace a service rather than stomping on the class itself.
  • Custom ObjectModel: Need to add fields to a core entity? Build your own ObjectModel with its own table rather than patching the existing one.

Keeping Overrides Safe Through Updates

PrestaShop updates can silently change method signatures, leaving your override running on stale assumptions. Treat every override as something to revisit after each update. Comment what you changed and why, check the changelog, and keep the override as small as possible. A narrow, well-commented override that calls parent methods properly survives upgrades. A full class copy is a bug waiting to surface.

The “Ghost” Cache: Why Your Override Seems Invisible

The number one reason developers think their override is broken: PrestaShop doesn’t scan /override/ on every request. It compiles a class map into /cache/class_index.php, and until that file is deleted and regenerated, your override is invisible, no matter how correct the code. Clearing the Smarty cache is not enough. Delete class_index.php manually via FTP/SSH, through Advanced Parameters > Performance in the back office, or via the Symfony console.

Figure 1: The PrestaShop override system flow and the role of /cache/class_index.php.

 

The Symfony Wall: Legacy Overrides Don’t Work for Modern Controllers

This legacy PHP system only works on PrestaShop’s older ObjectModel layer. From PS 1.7 onward, key back office areas, including the new Product Page and Order screens — have been rewritten in Symfony. The PHP override system does not affect these Symfony controllers. For those, use Service Decoration, wrapping or replacing a service via your module’s services.yml file. If an override has no effect even after clearing class_index.php, the controller is likely Symfony-migrated.

Conflict Resolution: When Two Modules Want the Same Class

When two modules both override the same class, PrestaShop throws an error on the second installation and leaves its override inactive. The fix is manual: open both files, merge all methods into a single /override/ file that extends the Core class, then delete class_index.php. Add comments identifying which module contributed each method. This is why overrides are banned in marketplace modules, conflicts become the merchant’s problem. If building for others, use hooks.

Figure 2: How override conflicts arise when two modules target the same class, and the manual merge resolution.

About Knowband

Knowband is a trusted eCommerce plugin and extension provider with years of experience helping online merchants build faster, smarter, and more feature-rich stores. Whether you run your store on PrestaShop, OpenCart, Shopify, WooCommerce, Magento, or any other major platform, Knowband offers a wide range of ready-to-use plugins and extensions that help you enhance functionality, improve performance, and deliver a better shopping experience, without needing to build from scratch.

How to Copy cart into another cart in order to change the id_cart?

Objectives of the blog

After reading this blog we will be able to copy the cart into another cart in order to change the Cart Id.

Requirement

We need to modify the below file to fix the same. We will be making changes in the Core file for the same.

Root dir\classes\PaymentModule.php

Procedure

We need to modify the validate order function in the PaymentModule class file. As this function is called only once while completing the order. So we have added the code in the same to avoid multiple cart creation.

 

if (self::DEBUG_MODE) {
PrestaShopLogger::addLog('PaymentModule::validateOrder - Function called', 1, null, 'Cart', (int)$id_cart, true);
}
if (!isset($this->context)) {
$this->context = Context::getContext();
}
$this->context->cart = new Cart((int)$id_cart);

 

Below code needs to be added in the validate order function after above lines.

// changes started
// To save the secure key of current cart id and reassign the same to new cart
$old_cart_secure_key = $this->context->cart->secure_key;
// To save the customer id of current cart id and reassign the same to new cart
$old_cart_customer_id = (int)$this->context->cart->id_customer;
// To unmap the customer from old cart
$this->context->cart->id_customer = 0;
// To update the cart
$this->context->cart->save();
// To fetch the current cart products
$cart_products = $this->context->cart->getProducts();
// Creating new cart object
$this->context->cart = new Cart();
$this->context->cart->id_lang = $this->context->language->id;
$this->context->cart->id_currency = $this->context->currency->id;
// to add new cart
$this->context->cart->add();
// to update the new cart
foreach ($cart_products as $product) {
$this->context->cart->updateQty((int) $product['quantity'], (int) $product['id_product'], (int) $product['id_product_attribute']);
}
if ($this->context->cookie->id_guest) {
$guest = new Guest($this->context->cookie->id_guest);
$this->context->cart->mobile_theme = $guest->mobile_theme;
}
// to map the new cart with the customer
$this->context->cart->id_customer = $old_cart_customer_id;
// to save the new cart
$this->context->cart->save();
if ($this->context->cart->id) {
$this->context->cookie->id_cart = (int) $this->context->cart->id;
$this->context->cookie->write();
}
// to update the $id_cart with that of new cart
$id_cart = (int) $this->context->cart->id;
$this->context->cart->secure_key = $old_cart_secure_key;
// changes over

 

After following the above procedure, the shopping cart list will look like this i.e having a duplicate cart with the same details as of the cart with which the order is placed.

Summary

After following the above procedure, You will be able to copy the cart into another cart in order to change the id_cart.

Get latest products without special prices (Opencart 2.3)

Objectives of the blog

After reading this blog you will be able to fetch products from Opencart database on the basis of parameter which is passed from URL.

Requirement

We have to display latest products on the basis of passed parameter in the URL. Using this parameter we have to decide that whether we have to display special products or not.

Code Implementation

To implement this we need to make change in our controller and model. In controller we added condition to pass parameter and in model we will make changes in the query according to the passed parameter.

To handle condition we are using show_special parameter in the code.

Below is the code which we have to add in controller (catalog/controller/extension/module/latest.php):

 

/*Checking for show_special key in get parameter
* This key ($show_special) will be used to conditionally fetch data from the database.
* i.e. if $show_special = 1 then display products which special
*         $show_special = 0 then display products which are not having special
*      else
*         display all products irrespective of special
*/
if (isset($this->request->get['show_special'])) {  
     //passing the value in variable if set in URL
     $show_special = $this->request->get['show_special'];
} else {
    //Otherwise empty string will be passed
    $show_special = '';
}
               
//Creating a variable to be passed into model
$filter_data = array(
'start' => ($page - 1) * $limit,
'limit' => $limit,
     'show_special' => $show_special
);
               
//Calling model function to fetch data from database according to the data passed in the filter_data array.
$results = $this->model_catalog_product->getProductLatest($filter_data);

 

In this code first we have checked that if ‘show_special’ parameter is set in the URL or not. If yes then set value of parameter in a variable $show_special and if no then set variable as blank.

Then we have created an array ($default_data) with the values on behalf of which we have to fetch products from database.

Then we have called model function getProductLatest() to fetch latest products according to the variable passed ($filter_data) in the function.

Here is the function which we have to add in model (catalog/model/catalog/product):

 

public function getProductLatest($data = array()) {
   //Setting $join and $condition parameters to fetch data of special products
   if($data['show_special'] == '1'){
        $join = "LEFT JOIN " . DB_PREFIX . "product_special ps  ON (ps.product_id = p.product_id) ";
        $condition = "AND ps.customer_group_id = '" . (int)$this->config->get('config_customer_group_id') . "'  
                             AND ((ps.date_start = '0000-00-00' OR ps.date_start < NOW()) AND (ps.date_end = '0000-00-00' OR ps.date_end > NOW() OR ps.date_end = '0001-01-01')) ";
   }
   //Setting $join and $condition parameters to fetch data of products which are not specil
   elseif($data['show_special'] == '0'){  
       $join = "LEFT JOIN " . DB_PREFIX . "product_special ps  ON (ps.product_id = p.product_id) ";
       $condition = "AND ps.product_id IS NULL";
   }else{
   //Setting $join and $condition parameters to fetch data of all products irrespective of special
       $join= '';
       $condition = '';
   }
           
   //Query to fetch data of products from database. Added $join and $condition parameter in the query to fetch data accordingly
   $sql = "SELECT DISTINCT p.product_id  
           FROM " . DB_PREFIX . "product p  
           LEFT JOIN " . DB_PREFIX . "product_description pd ON (p.product_id = pd.product_id)  
           LEFT JOIN " . DB_PREFIX . "product_to_store p2s ON (p.product_id = p2s.product_id)  
           $join
           WHERE p.status = '1' AND p.date_available <= NOW() AND p2s.store_id = '" . (int)$this->config->get('config_store_id') . "'  
           $condition
           GROUP BY p.product_id ORDER BY p.product_id DESC";
           
   //Setting limit in query if passed LIMIT from controller.
   if (isset($data['start']) || isset($data['limit'])) {
          if ($data['start'] < 0) {
                $data['start'] = 0;
          }
 
          if ($data['limit'] < 1) {
                $data['limit'] = 20;
          }
 
          $sql .= " LIMIT " . (int)$data['start'] . "," . (int)$data['limit'];
   }
 
   $product_data = array();
   //Execute query
   $query = $this->db->query($sql);
           
   //Loop to set fetched data in the variable to pass in controller.
   foreach ($query->rows as $result) {
        $product_data[$result['product_id']] = $this->getProduct($result['product_id']);
   }
   //Return products data to controller.
   return $product_data;
}

 

In model we have set variables $join and $condition on the basis of following three conditions:

  1. If show_special is 1: In this case we have displayed only products those are special. As in opencart the data of special products is stored in oc_product_special table so we have added join of oc_product table with the oc_product_secial table to fetch data of special products only.
  2. If show_special is 0: In this case we have displayed products which are not set as special. So to find products which are not special we have to fetch products which are in special table but on in oc_product_special table. For this we have added LEFT join of oc_product table with oc_product_secial table and added condition “ps.product_id IS NULL”.
  3. If show_special is other than 0 and 1: In this case we have to fetch all products whether they are special or not. So for this we have directly fetched data from oc_product table.

After adding all three possible condition we have written query and executed it. Then we have set all fetched data in $product_data array and return the same to controller.

Note: here to fetch latest products we have just used ‘Order by product_id DESC’ in query. If you want to fetch data of products which were added in last few days then you can add one more condition in the WHERE clause i.e. date_added > ‘any date’.

Summary

After following the above procedure, you will be able to fetch latest special products on the basis of passed parameter for special products in the URL.