System Overrides & Boot Hooks (the escape hatch)
- Version
- 4.0 preview
- Updated
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… | Prefer | Not this |
|---|---|---|
| Add a new controller (no core file of that name) | customization controllers/ | system_overrides |
| A model's data/fields | model_property_customization/ | system_overrides |
| A model's behavior by reacting to store/copy/delete | an observer | system_overrides |
| A screen's markup | a template override | system_overrides |
| Displayed text | language_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 beforeonConfigboxInitialized(so its file was alreadyrequired), then defining the same class name insystem_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.
3.1 Subclass core (recommended for controllers/models/views)
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
ConfigboxCurrencyHelperin 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:
| Function | Read by | Purpose | Guide |
|---|---|---|---|
cbGetCustomRequirePaths() | helpers/view.php:107 | add RequireJS module paths (appConfig.customPaths) | com_configbox_assets_and_amd.md §5 |
cbGetCustomRequireShims() | helpers/view.php:112 | add RequireJS shims | com_configbox_assets_and_amd.md §5 |
getPostDbConnectQueries() | KenedoDatabase.php:83-90 | SQL 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 event | com_configbox_custom_question_types.md (the code-composition pattern) |
getReleaseNumber() | helpers/view.php:20-21 | append a custom token to the asset cache-buster (?version=), so customization CSS/JS updates invalidate browser caches — bump the returned string whenever customization assets change | com_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)
- Exhaust targeted mechanisms first (§1). Only override a class if nothing smaller fits.
- Confirm the target isn't loaded during early boot (the redeclare limit, §2) — test on a real page.
- Define the class in
system_overrides/<Class>.phpwith 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. - For hooks/boot code, define plain functions (
cbGetCustomRequirePaths,getPostDbConnectQueries, …) — these are safe and don't hit the redeclare limit (§4–§5). - For per-install boot tweaks, use
data/store/private/settings/instead of shipping a system override (§6). - Verify manually — exercise the affected behavior, watch the error log (system overrides run with
display_errorson,overrides.php:34), and confirm DB-connect queries took effect. CBX has zero automated tests. - 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 keepparent::(§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 beforesystem_overrides/(§2.1, §6).- Hook functions are plain functions — safe to define here (
cbGetCustomRequirePaths,cbGetCustomRequireShims,getPostDbConnectQueries) (§4–§5). getInitQueries.phpruns 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 fromdata/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 everywhere —
getQuoted()backtick-quotes identifiers,getEscaped()escapes values (inside quotes you add) — or bind values withsetPreparedQuery($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.php—loadOverrideFiles()(: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) — thegetInitQueries.php/getPostDbConnectQuerieshook.