This article is intended for the https://opencartforum.com/ forum. It is tailored for add-on developers and is written in a concise and straightforward manner. It includes a brief history, nuances, a list of arguments for event handlers, and an example of a simple and clear implementation of a large number of events in applications for OpenCart 2.3, 3.x, 4.x. You can see module controller examples at the end of the article.
How it all began
Before version 2.0 (2014), we had to modify the engine codebase by directly interfering with the code or using vqmod. However, using vqmod caused problems when multiple modifiers were applied to the same section of code, as well as issues with add-on support. Implementing hooks helped to address some of these issues. The first implementation of “Override Engine” that I knew of was in 2012, along with the discussion of “hook pre render Idea and rough implementation”. In version 2.0 (2014), the engine introduced its first built-in event mechanism (hooks), and the vqmod concept was integrated into the engine itself and was called ocmod. Subsequently, in version 2.2 (2016), the trigger paths in events were changed to become similar to routes. In version 3.x (2017), the event mechanism and ocmod remained largely unchanged. However, from version 4.x onwards, ocmod will no longer be supported.
“Embrace, extend and extinguish“. However, we will always have vqmod.
Why do we need events?
Events allow you to run custom functions before or after calling any function in the MVCL+Config+Library paradigm to change input/output data. According to the engine maintainer’s idea, they should replace vqmod/ocmod.
I’ve already rewritten some old modules using events. For instance, the module “custom template” was previously embedded in the code of the main sections of the catalog and replaced their templates using ocmod. Now, it can replace any template in the engine. Events seem to solve certain types of problems very effectively.
Nuances when using events
- Events can only be added from admin controllers. The most convenient way to do this is during module installation, in the install function.
- All trigger paths begin with the name of the desired section: admin, catalog, or library. The admin and catalog sections contain controller, view, language, and config.
- In trigger paths, you can use the “*” sign to assign triggers by mask. For example, catalog/view/*/template/common/header/after.
- To change data in the config and language event handlers, use $this->config->set() and $this->language->set() respectively.
- Data obtained from event handlers can be saved inside the class and used in other handlers that are launched later.
To edit events from the admin panel, use “Event Manager” or adminer (Ctrl+click) for quick editing of a record.
Nuances for different engine versions
- The event code for version 2.3 must not exceed 32 characters. For versions 3.x and 4.x, the limit is 64 characters.
- In version 2.3, events such as registration and deletion are managed by the extension/event model, while in 3.x and 4.x, they are handled by the setting/event model.
- In version 2.3, the paths for view triggers (view) before/after will vary in the catalog section. For instance, catalog/view/common/header/before and catalog/view/default/template/common/header/after. This is due to the utilization of design templates in the catalog section.
- Starting from version 3.x, an event sorting order has been introduced.
- In version 4.x, each event must include a description. Triggers for libraries (library) are only accessible from version 4.x onwards.
Arguments passed
Arguments are passed by reference to event handlers. You can change their values without worrying about passing the result elsewhere.
2.3 | controller | model | view | language | config | |
before | $route, $data | $route, $args | $route, $data, $output | $route | ||
after | $route, $data, $output | $route, $args, $output | $route, $args, $output | $route, $output | $route | |
3.x | controller | model | view | language | config | |
before | $route, $args | $route, $args | $route, $data, $code | $route, $key | $route | |
after | $route, $data, $output | $route, $args, $output | $route, $data, $output | $route, $key, $output | $route | |
4.x | controller | model | view | language | config | library |
before | $route, $args | $route, $args | $route, $data, $code | $route, $prefix, $code | $route | $route, $args |
after | $route, $data, $output | $route, $args, $output | $route, $data, $output | $route, $prefix, $code, $data | $route, $data | $route, $args |
Return values
In addition to modifying data using arguments, event handlers can also return values using the “return” statement. For example, if the “controller/common/home/before” event handler returns the generated HTML code using “return,” then the entire output of the “common/header” controller will be replaced by it. The “common/header” controller itself will not be executed, but the “after” event will still be triggered. This means that you can replace the data of a function’s execution without actually running the function itself.
2.3 | controller | model | view |
before | mixed | mixed | string |
after | mixed | mixed | string |
3.x | controller | model | view |
before | mixed | mixed | string |
after | mixed | mixed | string |
4.x | controller | model | view |
before | mixed | ||
after | mixed |
Simple and clear implementation
<?php class ControllerExtensionModuleSample extends Controller {
/*
* all existing events
*/
private $_events = [
// admin events
[
"code" => "sample_394beb748918d3ce260756703",
"trigger" => "admin/controller/design/layout/before",
"action" => "extension/module/sample/acdl_b",
],
[
"code" => "sample_7a2b613ccb07a2c0e9c8cb844",
"trigger" => "admin/view/design/layout_list/after",
"action" => "extension/module/sample/avdll_a",
],
[
"code" => "sample_968b25d7939ec60e0008d670c",
"trigger" => "admin/model/design/layout/getLayouts/after",
"action" => "extension/module/sample/amdlgl_a",
],
// catalog event
[
"code" => "sample_172e1deab50793d6c4bec3b42",
"trigger" => "catalog/model/design/layout/getLayoutModules/after",
"action" => "extension/module/sample/filter",
],
];
/*
* event handler functions
*/
public function acdl_b(&$route, &$args) {
# code
}
public function avdll_a(&$route, &$data, &$output) {
# code
}
public function amdlgl_a(&$route, &$args, &$output) {
# code
}
/*
* extension install/remove functions
*/
public function install() {
$this->load->model('extension/event');
foreach($this->_events as $event) {
if(
!$result = $this->model_extension_event->getEvent(
$event['code'],
$event['trigger'],
$event['action']
)
) {
$this->model_extension_event->addEvent(
$event['code'],
$event['trigger'],
$event['action']
);
}
}
}
public function uninstall() {
$this->load->model('extension/event');
foreach($this->_events as $event) {
$this->model_extension_event->deleteEvent($event['code']);
}
}
/*
* extension code
*/
public function index() {
# code
}
}
<?php class ControllerExtensionModuleSample extends Controller {
/*
* all existing events
*/
private $_events = [
// admin events
[
"code" => "sample_394beb748918d3ce260756703",
"trigger" => "admin/controller/design/layout/before",
"action" => "extension/module/sample/acdl_b",
],
[
"code" => "sample_7a2b613ccb07a2c0e9c8cb844",
"trigger" => "admin/view/design/layout_list/after",
"action" => "extension/module/sample/avdll_a",
],
[
"code" => "sample_968b25d7939ec60e0008d670c",
"trigger" => "admin/model/design/layout/getLayouts/after",
"action" => "extension/module/sample/amdlgl_a",
],
// catalog event
[
"code" => "sample_172e1deab50793d6c4bec3b42",
"trigger" => "catalog/model/design/layout/getLayoutModules/after",
"action" => "extension/module/sample/filter",
],
];
/*
* event handler functions
*/
public function acdl_b(&$route, &$args) {
# code
}
public function avdll_a(&$route, &$data, &$output) {
# code
}
public function amdlgl_a(&$route, &$args, &$output) {
# code
}
/*
* extension install/remove functions
*/
public function install() {
$this->load->model('setting/event');
foreach($this->_events as $event) {
if(!$result = $this->model_setting_event->getEventByCode($event['code'])) {
$this->model_setting_event->addEvent(
$event['code'],
$event['trigger'],
$event['action']
);
}
}
}
public function uninstall() {
$this->load->model('setting/event');
foreach($this->_events as $event) {
$this->model_setting_event->deleteEventByCode($event['code']);
}
}
/*
* extension code
*/
public function index() {
# code
}
}
<?php
/**
* @author Shashakhmetov Talgat <talgatks@gmail.com>
*/
namespace Opencart\Admin\Controller\Extension\Sample\Module;
class Sample extends \Opencart\System\Engine\Controller {
/*
* all existing events
*/
private $_events = [
// admin events
[
'desc' => 'admin controller design/layout before',
'code' => 'sample_394beb748918d3ce260756703',
'trigger' => 'admin/controller/design/layout/before',
'action' => 'extension/sample/module/sample.acdl_b'
],
[
'desc' => 'admin view design/layout_list after',
'code' => 'sample_7a2b613ccb07a2c0e9c8cb844',
'trigger' => 'admin/view/design/layout_list/after',
'action' => 'extension/sample/module/sample.avdll_a'
],
[
'desc' => 'admin model design/layout/getLayouts after ',
'code' => 'sample_968b25d7939ec60e0008d670c',
'trigger' => 'admin/model/design/layout/getLayouts/after',
'action' => 'extension/sample/module/sample.amdlgl_a'
],
// catalog event
[
'desc' => 'catalog model design/layout/getModules after',
'code' => 'sample_172e1deab50793d6c4bec3b42',
'trigger' => 'catalog/model/design/layout/getModules/after',
'action' => 'extension/sample/module/sample.cmdlgm_a'
]
];
/*
* event handler functions
*/
public function acdl_b(string &$route, array &$data): void {
#code
}
public function avdll_a(string &$route, array &$data, string &$output): void {
#code
}
public function amdlgl_a(string &$route, array &$args, array &$output): void {
#code
}
/*
* extension install/remove functions
*/
public function install(): void {
$this->load->model('setting/event');
foreach($this->_events as $event) {
if(!$result = $this->model_setting_event->getEventByCode($event['code'])) {
$this->model_setting_event->addEvent(
$event['code'],
$event['desc'],
$event['trigger'],
$event['action']
);
}
}
}
public function uninstall(): void {
$this->load->model('setting/event');
foreach($this->_events as $event) {
$this->model_setting_event->deleteEventByCode($event['code']);
}
}
/*
* extension code
*/
public function index(): void {
#code
}
}