Skip to main content
Version: 3.x

System Overrides & Boot Hooks (the escape hatch)

Version
3.x
Updated
View markdown

The last-resort customization mechanism: replacing a framework/helper class that has no per-type loader, and running your own PHP at boot (or at DB-connect) — defining global hook functions, registering RequireJS paths, seeding settings. These are the most powerful and the most dangerous tools in the layer, because a class override replaces the whole class and you inherit the job of keeping it in sync with every core update.

Reach for this only when a targeted mechanism doesn't fit. The earlier guides route here for one core need: changing the behavior of an existing core class — a controller, model, or view — which the per-type controllers//models//views/ folders can't do (they're core-first, so a same-named file there is never loaded). It is also where the boot-time hook functions other guides reference live (cbGetCustomRequirePaths, getPostDbConnectQueries, …).

Read com_configbox_customization_overview.md first. All paths are relative to the component root docroot/components/com_configbox/. Source references are point-in-time (component 3.4.1) — verify against the code.


1. Why this is the escape hatch (use a targeted mechanism first)

Before using anything here, confirm none of these fits — they're smaller and don't drift:

To change…PreferNot this
Add a new controller (no core file of that name)customization controllers/system_overrides
A model's data/fieldsmodel_property_customization/system_overrides
A model's behavior by reacting to store/copy/deletean observersystem_overrides
A screen's markupa template overridesystem_overrides
Displayed textlanguage_overrides/system_overrides

system_overrides/ is justified when the thing you must change is a framework or helper class (e.g. a Kenedo* class, a Configbox*Helper) that no per-type loader covers, and reacting/extending isn't enough. Even then, prefer asking the vendor for a hook.


2. How the boot override works (and its one hard limit)

CBX's class autoloader is lazy: init.php only registers class→path mappings (KenedoAutoload::registerClass()), and a class file is required the first time that class is referenced (KenedoAutoload::loadClass(), external/kenedo/classes/KenedoAutoload.php:6-14). Nothing core is loaded just by booting.

At the end of boot, init.php fires onConfigboxInitialized (helpers/init.php:98), which runs ObserverSystem::onConfigboxInitialized()ConfigboxOverridesHelper::loadOverrideFiles() (observers/System.php:14). That method eagerly require_once's your override files (helpers/overrides.php:4-44). So a class you define in a system_overrides/ file is already in memory before core code first references that class name — and because the class now exists, PHP's autoloader is never invoked for it, so the core file that would have defined it is never loaded. That's the whole trick: you occupy the class name first.

// helpers/overrides.php (abridged) — runs at onConfigboxInitialized
$folder = KenedoPlatform::p()->getDirCustomizationSettings(); // 1) settings dir first
foreach (sorted($folder/*.php) as $file) require_once($file);

$folder = KenedoPlatform::p()->getDirCustomization().'/system_overrides'; // 2) then system_overrides
foreach (sorted($folder/*.php) as $file) require_once($file); // (display_errors on for these)

⚠️ The hard limit: a class can't already be loaded. Eager pre-emption only works for classes not yet referenced when loadOverrideFiles() runs. If some code touched the core class before onConfigboxInitialized (so its file was already required), then defining the same class name in system_overrides/ causes a fatal "cannot redeclare class". In practice most domain/helper classes are loaded well after boot, so this works — but verify your target isn't loaded during early boot, and test.

Two ways to define the winning class (§3): a wholesale replacement (declare the class from scratch, reproducing core methods) or — when you can locate the core file — a subclass: require the core file, class_alias() it to a side name, then declare the original name extending the alias. The subclass form keeps parent:: available and is much thinner; it works well for component classes (controllers/models/views) whose file path you know. Use wholesale replacement for framework/helper classes with no convenient core file to require.

2.1 Load order & the _ prefix

Within each folder, files are sorted with underscore-prefixed names first, then case-insensitive alpha (helpers/overrides.php:13-18,28-33). Use a leading _ on a file that must load before the others (e.g. a base class your other overrides extend). The settings dir loads before system_overrides/, so settings files can define things the overrides rely on.


3. Overriding a class via system_overrides/

Drop a file under data/customization/system_overrides/ that declares the class with the same name as the core one. You have two patterns — prefer the subclass form whenever you can require the core file.

When the core class lives in a file you can require by path, keep all core behavior and override only what you need: require the core file, alias its class to a side name, then declare the original name extending the alias. Because your class is defined first, the autoloader never loads core under that name again.

// data/customization/system_overrides/ConfigboxControllerCart.php
<?php
defined('CB_VALID_ENTRY') or die();

require_once KenedoPlatform::p()->getComponentDir('com_configbox').'/controllers/cart.php';
class_alias('ConfigboxControllerCart', 'ConfigboxControllerCartCore'); // keep a handle on core

class ConfigboxControllerCart extends ConfigboxControllerCartCore {
function add() {
// pre-step …
parent::add(); // ← core behavior still available
// post-step …
}
}

This is the thinnest, lowest-drift way to change a core controller, model, or view — you track only the method signatures you touch, not the whole class. Swap the require path for models/<name>.php or views/<view>/view.html.php as needed.

3.2 Wholesale replacement (framework/helper classes)

When there's no convenient core file to require — a Kenedo* framework class, a Configbox*Helper — declare the class from scratch with the same name. There's no parent::, so reproduce the methods you don't change: start from the core class and edit a copy, keeping the diff small.

// data/customization/system_overrides/ConfigboxCurrencyHelper.php
<?php
defined('CB_VALID_ENTRY') or die();

// Same class name as core → this definition wins (loaded before core would autoload it).
class ConfigboxCurrencyHelper {

// … copy the core class body, change only what you need …

public static function getFormatted($price, $symbol = true, $emptyOnZero = false, $decimals = 2) {
// your altered formatting
}

// … all other core methods reproduced verbatim …
}

You now own this class. Every core bug fix or change to ConfigboxCurrencyHelper in future updates will not reach your install — your copy shadows it. Re-diff against the core class after every component update. Keep the override as thin as the language allows; if the class is large, consider whether an observer or a different seam avoids the copy.


4. Boot hook functions (run code, register hooks)

system_overrides/ files (and settings-dir files) are plain PHP run at boot, so they're also where you define the global functions other parts of CBX look for. The framework calls a function if it exists:

FunctionRead byPurposeGuide
cbGetCustomRequirePaths()helpers/view.php:107add RequireJS module paths (appConfig.customPaths)com_configbox_assets_and_amd.md §5
cbGetCustomRequireShims()helpers/view.php:112add RequireJS shimscom_configbox_assets_and_amd.md §5
getPostDbConnectQueries()KenedoDatabase.php:83-90SQL to run right after each DB connect (§5)this guide
postMakeSelection(&$response)controllers/configuratorpage.php sendResponse()append data to EVERY makeSelection response — the seam for anything a widget must update live per selection change (a composed product code, a stock indicator). The parameter is by reference; add keys, never replace the array. A frontend module reads them off the serverResponseReceived eventcom_configbox_custom_question_types.md (the code-composition pattern)
getReleaseNumber()helpers/view.php:20-21append a custom token to the asset cache-buster (?version=), so customization CSS/JS updates invalidate browser caches — bump the returned string whenever customization assets changecom_configbox_assets_and_amd.md
// data/customization/system_overrides/_hooks.php (leading _ → loads early)
<?php
defined('CB_VALID_ENTRY') or die();

function cbGetCustomRequirePaths() {
return array('mylib' => KenedoPlatform::p()->getUrlCustomizationAssets().'/javascript/vendor/mylib.min');
}

These are plain global functions, not classes — so they don't hit the redeclare limit (§2). Defining them in system_overrides/ (or the settings dir) is the supported way to register boot-time hooks.


5. The DB-connect hook — getInitQueries.php

Separate from the loadOverrideFiles() flow and fired earlier: right after each database connection, KenedoDatabase includes system_overrides/getInitQueries.php (if present) and runs the queries returned by getPostDbConnectQueries() (external/kenedo/classes/KenedoDatabase.php:83-90):

// data/customization/system_overrides/getInitQueries.php
<?php
defined('CB_VALID_ENTRY') or die();

function getPostDbConnectQueries() {
return array(
"SET SESSION sql_mode = ''", // e.g. relax a strict mode for legacy data
"SET SESSION group_concat_max_len = 1000000",
);
}

Use it for session-level DB settings the whole request needs (SQL mode, timezone, concat limits). It runs on every connection — keep the list short and side-effect-free. This file is special-cased by name; it does not need a class.


6. Boot-time settings files (data/store/private/settings/)

loadOverrideFiles() loads a second set of PHP files first: those in getDirCustomizationSettings() = data/store/private/settings/ (general.php:1015). Same eager require_once, same _-first ordering, loaded before system_overrides/. This is a separate slot from data/customization/ (it lives under data/store/private/), intended for install-local boot settings (e.g. defining constants/hook functions for one deployment). For shipped/versioned customization code, prefer system_overrides/; use the settings dir for per-install boot tweaks that shouldn't travel with the code. Both run at the same boot point and can define the hook functions in §4.


7. Deployment checklist

data/customization/
system_overrides/
_<base>.php ← (optional) loads first (leading underscore) — e.g. a shared base
<Class>.php ← replace a core class (same class name; reproduce unchanged methods)
_hooks.php ← define cbGetCustomRequirePaths / cbGetCustomRequireShims, etc.
getInitQueries.php ← getPostDbConnectQueries() — SQL per DB connect (special-cased)
data/store/private/settings/
*.php ← per-install boot settings (loaded before system_overrides, not shipped code)
  1. Exhaust targeted mechanisms first (§1). Only override a class if nothing smaller fits.
  2. Confirm the target isn't loaded during early boot (the redeclare limit, §2) — test on a real page.
  3. Define the class in system_overrides/<Class>.php with the same name as core — subclass it (require+alias+extend) for a controller/model/view (§3.1), or replace it wholesale for a framework/helper class (§3.2). Use a leading _ if another override must load after a shared base.
  4. For hooks/boot code, define plain functions (cbGetCustomRequirePaths, getPostDbConnectQueries, …) — these are safe and don't hit the redeclare limit (§4–§5).
  5. For per-install boot tweaks, use data/store/private/settings/ instead of shipping a system override (§6).
  6. Verify manually — exercise the affected behavior, watch the error log (system overrides run with display_errors on, overrides.php:34), and confirm DB-connect queries took effect. CBX has zero automated tests.
  7. After every component update, re-diff each overridden class against the new core version — this is the standing maintenance cost of this mechanism.

8. Conventions & gotchas

  • Escape hatch, not default. Prefer controllers/templates/property-injection/observers/language-overrides; override a class only when no per-type seam exists (§1).
  • Same class name wins by loading first — but only if the class isn't already loaded when overrides run; otherwise it's a fatal redeclare (§2). For component classes you can require+alias+subclass to keep parent:: (§3.1); for framework/helper classes you replace wholesale (§3.2).
  • You inherit maintenance. A replaced class won't get core updates; re-diff after every upgrade. Keep overrides minimal.
  • _-prefixed files load first, settings dir before system_overrides/ (§2.1, §6).
  • Hook functions are plain functions — safe to define here (cbGetCustomRequirePaths, cbGetCustomRequireShims, getPostDbConnectQueries) (§4–§5).
  • getInitQueries.php runs on every DB connect — keep it minimal, session-level only (§5).
  • Settings dir is per-install, not shipped code (data/store/private/settings/) — different intent from data/customization/ (§6).
  • No top-level side effects beyond what you intend — these files run at boot, eagerly, every request. Guard with defined('CB_VALID_ENTRY') or die();.
  • Escape/sanitize as everywheregetQuoted() backtick-quotes identifiers, getEscaped() escapes values (inside quotes you add) — or bind values with setPreparedQuery($sql, $params) and skip manual escaping.

See also

  • com_configbox_customization_overview.md — the extension-point map (this is the eager pre-emption / boot-include mechanism) and the per-kind precedence rules that route here.
  • com_configbox_overriding_controllers_and_models.md — why changing an existing core controller, model, or view behavior lands here (the per-type folders are core-first).
  • com_configbox_assets_and_amd.md §5 — cbGetCustomRequirePaths/cbGetCustomRequireShims (defined here).
  • com_configbox_events_and_observers.md — usually a safer alternative to replacing a class.
  • helpers/overrides.phploadOverrideFiles() (:4): the two folders, _-first ordering, eager include.
  • helpers/init.php (:98) + observers/System.php (:14) — the boot point that triggers overrides.
  • external/kenedo/classes/KenedoAutoload.php — the lazy autoloader that makes pre-emption possible.
  • external/kenedo/classes/KenedoDatabase.php (:83) — the getInitQueries.php / getPostDbConnectQueries hook.