Overriding Controllers & Models (changing behavior)
- Version
- 3.x
- Updated
How to change what CBX does — a controller action, a model's query/validation/storage logic —
from the customization layer. The key fact to internalise first: the customization controllers/ and
models/ folders add new classes; they do not shadow existing core ones. Expecting a same-named file
to override a core controller or model is the single biggest source of "my override does nothing"
confusion. To change the behavior of an existing core class you use system_overrides/ (or, better,
an observer) — not the controllers//models/ folders.
Read com_configbox_customization_overview.md first. For changing a screen's markup see
com_configbox_overriding_views_and_templates.md; for adding fields/data to a model see
com_configbox_extending_stock_models.md. This guide is for behavior.
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. The precedence rule — read this first
All three class loaders — controllers, models, and view classes — resolve the same way: core first,
customization second. The customization file is loaded only when no core class of that name exists,
so the customization controllers//models//views/ folders are for new classes; none of them
shadows an existing core class.
| Kind | Loader | Checks first | Result |
|---|---|---|---|
| Controller | KenedoController::getController() (external/kenedo/classes/KenedoController.php:101-108) | core | Core wins → customization controllers/ is for new controllers only; it cannot shadow a core controller. |
| Model | KenedoModel::getModel() (external/kenedo/classes/KenedoModel.php:168-176) | core | Core wins → customization models/ is for new models only; it cannot shadow a core model. |
| View class | KenedoView::getView() (KenedoView.php:202-207) | core | Core wins → new views only. |
All three loaders share the same shape — core path first, customization path as the fallback:
// KenedoController::getController() (:101-108) — KenedoModel/KenedoView are identical in shape
if (is_file($regularPath)) { $path = $regularPath; } // ← core FIRST → core wins
elseif (is_file($customPath)) { $path = $customPath; } // only reached when no core class of that name
The one-line takeaway: you cannot override an existing core controller, model, or view by dropping a same-named file in
data/customization/. The customizationcontrollers//models//views/folders are for new classes. To change the behavior of an existing core class, replace it viasystem_overrides/(§5) — or, far better where it fits, react to it with an observer. Verify the precedence against your component version before relying on it.
A corroborating fact from the shipped customization layer: every customization controller and model in
Rovexo's own data/customization/ is a new one (zero share a file name with a core controller/model).
That matches the loader behavior — the folders only ever load new classes — and reflects how real
customization works in practice: add new controllers/models, and change existing behavior through
observers, property injection, templates, or system overrides.
2. The naming contract (both kinds)
Derived in the loaders from the class name:
| Kind | Class name | File |
|---|---|---|
| Controller | ConfigboxController<Name> | controllers/<name>.php (<name> = lowercase of the part after Controller) |
| Model | ConfigboxModel<Name> | models/<name>.php (<name> = lowercase of the part after Model) |
Examples: ConfigboxControllerCart → controllers/cart.php; ConfigboxModelProducts →
models/products.php (model classes carry no Admin prefix — only controllers do, so the admin
products controller is adminproducts while its model is products). The component is derived as
com_<lowercase-before-Controller/Model>, and a
legacy com_cbcheckout is rewritten to com_configbox (KenedoController.php:92-94,
KenedoModel.php:159-161).
Legacy
Cbcheckout*fallback. After loading the file, if the expectedConfigbox…class isn't defined, the loader looks for the oldCbcheckout…name and uses it with a deprecation log (KenedoController.php:122-126,KenedoModel.php:191-196). This exists for old customizations — write new code with theConfigbox…class name; don't rely on the fallback.
3. Controllers — you can add, but not shadow
Because controllers are core-first, the customization controllers/ folder is consulted only when no
core controller of that name exists (getController(), §1). So:
- Adding a new controller — supported and common. Drop
data/customization/controllers/<name>.phpwith classConfigboxController<Name>; it loads because there is no core file of that name. - Changing an existing core controller's behavior — the customization
controllers/folder will not do it (a same-named file is never reached). Replace the class viasystem_overrides/(§5), or, where you only need to react to an action rather than alter it, use an observer.
3.1 Adding a new controller
Declare the class extending the base KenedoController — exactly as the shipped customization controllers
do:
// data/customization/controllers/myexport.php (a NEW controller — no core 'myexport')
<?php
defined('CB_VALID_ENTRY') or die();
class ConfigboxControllerMyexport extends KenedoController {
function run() {
// … your action; reach the URL via index.php?option=com_configbox&controller=myexport&task=run
}
}
The file name is the lowercased part after Controller (§2): ConfigboxControllerMyexport →
controllers/myexport.php. A new controller usually needs a route or menu entry to reach it — see
../platform/joomla/com_configbox_sef_urls.md.
3.2 Changing an existing core controller's behavior
You cannot do this from the controllers/ folder. Use system_overrides/, which loads your class
before core would (so core never defines its version) — this both avoids the redeclaration conflict
and lets you subclass core cleanly. The pattern: load the core file, alias it, then declare the
dispatcher's class name extending the alias:
// data/customization/system_overrides/ConfigboxControllerCart.php
<?php
defined('CB_VALID_ENTRY') or die();
// Loaded eagerly at boot, BEFORE the lazy loader would pull core. Require the core file, keep a
// handle on its class under a different name, then declare the dispatcher's name extending it.
require_once KenedoPlatform::p()->getComponentDir('com_configbox').'/controllers/cart.php';
class_alias('ConfigboxControllerCart', 'ConfigboxControllerCartCore');
class ConfigboxControllerCart extends ConfigboxControllerCartCore {
function add() {
// pre-step …
parent::add(); // keep core behavior
// post-step (e.g. fire your own logic) …
}
}
Because your ConfigboxControllerCart is already defined when the dispatcher asks for it, the core
controllers/cart.php is never loaded as the dispatcher's class — you get a clean subclass with the name
the dispatcher expects. See §5 and the system-overrides guide for the boot mechanics and load order.
4. Models — you can add, but not shadow
The customization models/ folder is only consulted when no core model of that name exists
(getModel() is core-first, §1). So:
-
Adding a new model — supported. Put
data/customization/models/<newname>.phpwith classConfigboxModel<Newname>;getModel('ConfigboxModel<Newname>')finds it because there's no core file. Use this for models backing your own custom screens/controllers.If that model also returns a name from
getEntityName()— opting it into the REST API (com_configbox_entity_api.md) and the MCP tools (com_configbox_mcp_server.md) — it needs a scope area, so a token can be checked against the right<area>:read/<area>:writebefore it touches your entity. The central map (ConfigboxApiTokenHelper::getEntityAreas()) has no entry for a customization's entity, so it falls back tocatalog— right for content that belongs next to the products (a summary-panel image, say), wrong for anything else. OverridegetApiArea()whenever the fallback is wrong:function getApiArea() {return ConfigboxApiTokenHelper::AREA_CUSTOMERS;}Return one of
AREA_CATALOG,AREA_STORE,AREA_CUSTOMERS,AREA_ORDERS,AREA_CONVERSATIONS,AREA_CODEorAREA_SYSTEM. Skipping this is a real exposure, not just a mismatch: a custom model holding customer or order data that falls back tocatalogis readable by any token minted only to browse the catalog. -
Changing an existing core model — the customization
models/folder will not do it. Pick by what you're changing:- Data shape (add/remove/retune fields, list columns, storage of a field) →
model_property_customization/(the merge mechanism). Seecom_configbox_extending_stock_models.md. This is the right tool for most "I need the model to carry/expose X" needs and never touches behavior. - Behavior (a method body — a custom query, a validation rule that a property can't express, a
storage side-effect) → replace the class via
system_overrides/(§5). - React to a store/copy/delete rather than change it → an observer
(
onAfterStoreRecord,onAfterCopyRecord,onAfterDeleteRecord). Additive and far safer than replacing the class. Seecom_configbox_events_and_observers.md. - Check for a purpose-built hook first. Some core models deliberately ask another object rather
than decide for themselves, and overriding that object is both supported and a fraction of the
work. The clearest case:
ConfigboxModelOrderRecordfreezes an order line's SKU by callingConfigboxQuestion::getSku($selection), so a question type that derives a part number from a free-entry selection needs a ~10-line sub-class and no order-record override at all (com_configbox_custom_question_types.md§3).
- Data shape (add/remove/retune fields, list columns, storage of a field) →
Prefer observers and property injection over class replacement for models. Replacing a model class means copying its logic and maintaining it against every future core update — real drift risk on a ~218K-LOC component.
5. Behavior override via system_overrides/ (the reliable path for existing core classes)
system_overrides/*.php files are require_once'd eagerly at boot, before the lazy loaders would
pull the core class (helpers/overrides.php:24-39, fired from the onConfigboxInitialized observer). So a
class you define there is already in memory when getController()/getModel()/getView() run — and because the class
name is already defined, the core file's class … is never reached. That makes system_overrides/ the
way to change an existing core class' behavior (controller, model, or view) while keeping its
name — the one thing the per-type controllers//models//views/ folders cannot do.
You have two patterns:
- Subclass core (recommended — minimal override): require the core file, alias its class, then declare
the same name extending the alias, overriding only the methods you need. This keeps all core behavior.
See the worked controller example in §3.2 — the same pattern works for a model (require
models/<name>.php) or a view. - Wholesale rewrite: declare the class from scratch. Only when you genuinely replace everything; you then copy and maintain every core method you didn't change.
Either way, override the minimum and revisit after each component upgrade — a subclass tracks core method
signatures, a rewrite tracks whole methods. Full details, load order, and the boot/settings hooks live in
com_configbox_system_overrides_and_boot_hooks.md; this section points you there as the correct
destination for behavior overrides of any core controller, model, or view.
6. Deployment checklist
data/customization/
controllers/<name>.php ← a NEW controller (ConfigboxController<Name>); does NOT shadow a core controller
models/<name>.php ← a NEW model (ConfigboxModel<Name>); does NOT shadow a core model
system_overrides/<Class>.php ← change an existing core controller/model class' behavior (keeps the name)
- Classify the change: markup → templates guide; data/fields → stock-models guide; behavior → here.
- Controller, new → customization
controllers/(§3.1). - Controller, behavior change of an existing one →
system_overrides/(§3.2 / §5). - Model, new → customization
models/(§4). - Model, behavior change →
system_overrides/(§5). Model, data/field change →model_property_customization/. Model, react-only → an observer. - Use the
Configbox…class name (not legacyCbcheckout…) and the lowercase file name (§2). - Verify manually — exercise the real action/flow, including XHR-injected admin actions. CBX has zero automated tests.
- After each update, re-check any
system_overrides/class against the (possibly changed) core class.
7. Conventions & gotchas
- Controllers, models, and view classes all load core-first (new-only). The customization
controllers//models//views/folders add new classes; none shadows an existing core class. The single most important fact in this guide (§1). - To change an existing core class, use
system_overrides/. It loads your class before core would, so you can subclass core cleanly and keep the dispatcher's class name (§3.2, §5). The per-type folders can't do this — a same-named file there is never reached. - A customization
controllers/<corename>.phpis silently ignored. Because core wins, a same-named file never loads. Name new controllers distinctly; reach forsystem_overrides/to override an existing one. - Prefer additive over replace. Observers (react to events) and
model_property_customization/(add/retune fields) don't drift across updates; class replacement does. - Legacy fallback is a bridge, not an API. Write
Configbox…class names; theCbcheckout…fallback exists only for old code and logs a deprecation. - Escape on output, sanitize on input. SQL is traditionally hand-built mysqli:
getQuoted()backtick-quotes identifiers,getEscaped()escapes values (add the surrounding quotes yourself) — or bind values withsetPreparedQuery($sql, $params)and skip manual escaping. Either way validate request data before it reaches a query. - Match the idioms. No namespaces/PSR-4, static singletons,
KText::_()for strings. Follow neighboring controllers/models.
See also
com_configbox_customization_overview.md— the per-kind precedence table and the resolution rule.com_configbox_extending_stock_models.md— change a model's data/fields (no behavior change needed).com_configbox_events_and_observers.md— react to store/copy/delete and domain events (the safest way to inject model-adjacent behavior).com_configbox_system_overrides_and_boot_hooks.md— change an existing core class' behavior (the reliable path for core controllers, models, and views).com_configbox_overriding_views_and_templates.md— change a screen's markup / add a new view.../technical/com_configbox_kenedo_controller.md—KenedoControllerin depth: the layer under this guide — how a controller dispatches tasks, thestore()/delete()/copy()flows, and authorization. Read it before adding a custom task or overriding a core controller.external/kenedo/classes/KenedoController.php(getController,:101),…/KenedoModel.php(getModel,:168),…/KenedoView.php(getView,:181) — the three loaders, all core-first.