События в OpenCart: полная инструкция с примерами кода (2.x, 3.x, 4.x)
События в OpenCart: инструкция с примерами кода
Система событий (Events) — главный способ кастомизации OpenCart без правки файлов ядра. Событие — это «звонок в дверь»: OpenCart говорит «происходит вот это», а ваш код реагирует. Полная инструкция с примерами кода для OC 2.x, 3.x и 4.x — ниже.
Часть 0. Что такое «событие» — объясняем на пальцах
Представь звонок в дверь. Ты не сидишь весь день у двери в ожидании гостей — ты занимаешься своими делами. Когда кто-то нажимает кнопку — раздаётся звук, и ты идёшь открывать.
- Кнопка звонка — место в коде OpenCart, где происходит важное действие («товар добавили в корзину», «заказ создан»).
- Звук звонка — это и есть событие (event).
- Ты, идущий открывать дверь — обработчик события (listener / handler): код, который ты написал и который сработает, когда «услышит звонок».
OpenCart в разных местах кода «нажимает кнопку звонка» — говорит: «Эй, я сейчас добавляю товар в заказ!» или «Показываю страницу товара!». Разработчик может «подписаться» на этот звонок и вставить свой код — не трогая исходные файлы движка.
Это даёт три конкретных преимущества:
- Безопасность при обновлениях. Файлы ядра не редактируются — при обновлении OpenCart доработки сохраняются.
- Совместимость модулей. Несколько расширений могут «слушать» одно событие одновременно, не конфликтуя.
- Порядок и предсказуемость. У каждого обработчика есть приоритет (sort_order) — можно точно управлять, кто сработает раньше.
Четыре ключевых термина:
| Термин | Значение | Аналогия |
|---|---|---|
| Trigger (триггер) | Название/адрес места, где происходит событие | Номер конкретного звонка |
| Event / trigger() | Момент «звонка» — OpenCart сообщает о действии | Звук звонка |
| Action / Listener | Код, который выполнится в ответ | Ты, открывающий дверь |
| Register / addEvent() | Регистрация обработчика: «если услышишь этот звонок — позови меня» | Табличка на двери |
Часть 1. Как это работает «под капотом»
1.1 Три шага любого события
Шаг 1: OpenCart вызывает → $this->event->trigger('имя_события', $data);
Шаг 2: Система проверяет → «Есть ли кто-то, кто подписан?»
Шаг 3: Если да → вызывается ваш метод-обработчик с этими данными
1.2 Где хранятся подписки
Все зарегистрированные события — в таблице oc_event базы данных. Поля таблицы:
code— уникальный код события (для удаления)trigger— имя события, на которое реагируемaction— что вызвать в ответstatus— включено (1) или выключено (0)sort_order— приоритет: чем меньше число, тем раньше сработает
Посмотреть зарегистрированные события можно в админке: Расширения → События.
1.3 Приоритет (sort_order)
Допустим, на одно событие подписались два модуля: один начисляет скидку, другой — налог. Порядок вызова влияет на итоговую сумму. Поэтому важно задавать sort_order явно:
$this->model_setting_event->addEvent(
'discount_module',
'catalog/model/checkout/order/addOrder/before',
'extension/module/discount/apply',
1, // сработает первым
1 // статус: включено
);
$this->model_setting_event->addEvent(
'tax_module',
'catalog/model/checkout/order/addOrder/before',
'extension/module/tax/apply',
5, // сработает позже
1
);
1.4 Можно ли «остановить» событие?
Нет — стандартная система событий OpenCart не поддерживает «остановку» цепочки (в отличие от event.stopPropagation() в JavaScript). Все подписанные обработчики выполнятся по очереди, в порядке sort_order. Чтобы «заблокировать» действие, меняйте данные, которые OpenCart проверит после — или подменяйте $output в обработчиках after (см. часть 3).
Часть 2. OpenCart 2.x — первая версия событий
2.1 Устройство системы
В OpenCart 2.x события появились в версии 2.2 (2015 год). Класс Eventнаходятся файле system/engine/event.php:
class Event {
private $data = array();
public function register($key, $action) {
$this->data[$key][] = $action;
}
public function trigger($key, &$arg = array()) {
if (isset($this->data[$key])) {
foreach ($this->data[$key] as $event) {
$action = new Action($event, $arg);
$action->execute($this->registry);
}
}
}
}
В ядре OpenCart где-то стоит вызов $this->event->trigger('pre.order.add', $data) — это «звонок в дверь». Нам остаётся зарегистрировать обработчик на это имя.
2.2 Пример: логируем создание заказа
Шаг 1. Обработчик — файл catalog/controller/extension/event/order_notify.php:
<?php
class ControllerExtensionEventOrderNotify extends Controller {
// Параметры передаются по ссылке (&) — можно менять $data изнутри
public function notify(&$route, &$data) {
$this->log->write('Новый заказ! Товаров: ' . count($data['products']));
}
}
Шаг 2. Регистрация в контроллере установки модуля:
<?php
class ControllerExtensionModuleOrderNotify extends Controller {
public function install() {
$this->load->model('extension/event');
$this->model_extension_event->addEvent(
'order_notify',
'pre.order.add',
'extension/event/order_notify/notify'
);
}
public function uninstall() {
$this->load->model('extension/event');
$this->model_extension_event->deleteEvent('order_notify');
}
}
2.3 Несколько событий одним модулем
Один модуль может подписаться на несколько событий — просто вызывайте addEvent() несколько раз:
public function install() {
$this->load->model('extension/event');
$this->model_extension_event->addEvent(
'my_module_order', 'pre.order.add',
'extension/event/my_module/beforeOrder'
);
$this->model_extension_event->addEvent(
'my_module_status', 'post.order.history.add',
'extension/event/my_module/afterStatus'
);
$this->model_extension_event->addEvent(
'my_module_login', 'post.customer.login',
'extension/event/my_module/onLogin'
);
}
2.4 Меняем данные товара перед добавлением в корзину
<?php
class ControllerExtensionEventCartFix extends Controller {
public function fix(&$route, &$data) {
// Добавляем подарочный товар к определённому product_id
if (isset($data['product_id']) && $data['product_id'] == 42) {
$data['quantity'] += 1;
}
}
}
Часть 3. OpenCart 3.x — события «before» и «after»
3.1 Что изменилось
В 3.x система событий стала заметно мощнее. Теперь почти любой метод в контроллере, модели или view-файле можно «прослушать» — как до выполнения (before), так и после (after).
Имя события — путь к файлу + /before или /after:
| before | after | |
|---|---|---|
| Когда срабатывает | До выполнения кода | После выполнения кода |
| Изменить входные данные | Да, через $args |
Нет — метод уже отработал |
| Изменить результат | Нет — результата ещё нет | Да, через &$output |
| Типичное применение | Валидация, подмена параметров | Изменение HTML, уведомления, логирование |
3.2 Меняем модельный номер товара
<?php
class ControllerExtensionModuleModelFixer extends Controller {
public function fixModel(&$route, &$args, &$output) {
if (isset($args[0]['products'])) {
foreach ($args[0]['products'] as &$product) {
$product['model'] = 'WEB-' . $product['model'];
}
}
}
}
Регистрация:
$this->model_setting_event->addEvent(
'model_fixer',
'catalog/model/checkout/order/addOrder/before',
'extension/module/model_fixer/fixModel'
);
3.3 Добавляем JS-файл в шапку
<?php
class ControllerExtensionModuleAddScript extends Controller {
public function addScript(&$route, &$data) {
$data['scripts'][] = 'catalog/view/javascript/my_custom_script.js';
}
}
// Регистрация:
$this->model_setting_event->addEvent(
'add_script',
'catalog/view/common/header/before',
'extension/module/add_script/addScript'
);
3.4 Подменяем HTML после отрисовки шаблона
Событие after для view-файлов даёт доступ к готовому HTML через &$output. Удобно для «на лету» вставки баннера без правки .twig:
<?php
class ControllerExtensionModuleInsertBanner extends Controller {
public function insert(&$route, &$data, &$output) {
$banner = '<div class="my-banner"><img src="catalog/view/image/promo.jpg"></div>';
$output = str_replace(
'<div id="content">',
'<div id="content">' . $banner,
$output
);
}
}
// Регистрация:
$this->model_setting_event->addEvent(
'insert_banner',
'catalog/view/product/category/after',
'extension/module/insert_banner/insert'
);
3.5 Валидация — блокируем добавление товара без обязательной опции
Правильный способ «заблокировать» действие в 3.x — подменить $output так, будто произошла ошибка:
<?php
class ControllerExtensionModuleCartValidate extends Controller {
public function validate(&$route, &$args, &$output) {
// Товар 55 требует обязательного выбора цвета
if ($args[0] == 55 && empty($args[2]['option'])) {
$output = array(
'error' => array(
'warning' => 'Пожалуйста, выберите цвет!'
)
);
}
}
}
// Регистрация:
$this->model_setting_event->addEvent(
'cart_validate',
'catalog/controller/checkout/cart/add/before',
'extension/module/cart_validate/validate'
);
3.6 Уведомление администратора при смене статуса заказа
<?php
class ControllerExtensionModuleStatusMail extends Controller {
public function notify(&$route, &$args, &$output) {
$order_id = $args[0];
$order_status_id = $args[1];
$this->load->model('checkout/order');
$order = $this->model_checkout_order->getOrder($order_id);
$mail = new Mail($this->config->get('config_mail_engine'));
$mail->setTo($this->config->get('config_email'));
$mail->setFrom($this->config->get('config_email'));
$mail->setSender('Магазин');
$mail->setSubject('Статус заказа #' . $order_id . ' изменён');
$mail->setText('Новый статус: ' . $order_status_id);
$mail->send();
}
}
// Регистрация:
$this->model_setting_event->addEvent(
'status_mail',
'catalog/model/checkout/order/addOrderHistory/after',
'extension/module/status_mail/notify'
);
3.7 Добавляем пункт в левое меню админки
<?php
class ControllerExtensionModuleMenuItem extends Controller {
public function addMenu(&$route, &$data) {
$data['menus'][] = array(
'id' => 'menu-my-module',
'icon' => 'fa-star',
'title' => 'Мой модуль',
'href' => $this->url->link(
'extension/module/my_module',
'user_token=' . $this->session->data['user_token']
),
'children' => array()
);
}
}
// Регистрация:
$this->model_setting_event->addEvent(
'menu_item',
'admin/view/common/column_left/before',
'extension/module/menu_item/addMenu'
);
Часть 4. OpenCart 4.x — namespaces и новый формат
4.1 Главные отличия
В 4.x весь код переписан с использованием пространств имён (namespace). Регистрация событий теперь через один массив с ключами. Ещё одно важное изменение: система событий поддерживает wildcard-паттерны в триггерах — символ * заменяет любую последовательность символов, ? — один символ. Эторазрешает писать универсальные обработчики:
// Подписка на ВСЕ события каталога (через wildcard)
$this->model_setting_event->addEvent([
'code' => 'my_ext_all_catalog',
'trigger' => 'catalog/*/before',
'action' => 'extension/my_ext/events.onAnyCatalogAction',
'status' => 1,
'sort_order' => 1
]);
Внутри Event::trigger() паттерн преобразуется: * → .*, ? → ., и сравнивается с помощью preg_match().
4.2 Структура типового расширения
extension/
└── test_module/
├── admin/
│ ├── controller/module/test_module.php
│ ├── language/en-gb/module/test_module.php
│ └── view/template/module/test_module.twig
├── catalog/
│ └── controller/events.php
└── install.json
4.3 Пример: событие перед добавлением товара в корзину
Шаг 1. Обработчик — extension/test_module/catalog/controller/events.php:
<?php
namespace OpencartCatalogControllerExtensionTestModule;
class Events extends OpencartSystemEngineController {
public function onCartAddBefore(&$route, &$data, &$output = null) {
$this->log->write('onCartAddBefore: ' . print_r($data, true));
}
}
Шаг 2. Регистрация — extension/test_module/admin/controller/module/test_module.php:
<?php
namespace OpencartAdminControllerExtensionTestModuleModule;
class TestModule extends OpencartSystemEngineController {
public function install(): void {
$this->load->model('setting/event');
$this->model_setting_event->addEvent([
'description' => 'Test module — before cart add',
'code' => 'test_module_cart_add_before',
'trigger' => 'catalog/controller/checkout/cart.add/before',
'action' => 'extension/test_module/events.onCartAddBefore',
'status' => 1,
'sort_order' => 1
]);
}
public function uninstall(): void {
$this->load->model('setting/event');
$this->model_setting_event->deleteEventByCode('test_module_cart_add_before');
}
}
Обратите внимание на формат триггера: для методов, отличных от index, ставится точка перед именем метода (cart.add), а не слэш.
4.4 Несколько событий разом
public function install(): void {
$this->load->model('setting/event');
$events = [
[
'code' => 'my_ext_cart_before',
'trigger' => 'catalog/controller/checkout/cart.add/before',
'action' => 'extension/my_ext/events.onCartAddBefore'
],
[
'code' => 'my_ext_order_after',
'trigger' => 'catalog/model/checkout/order.addOrder/after',
'action' => 'extension/my_ext/events.onOrderAdded'
],
[
'code' => 'my_ext_admin_menu',
'trigger' => 'admin/view/common/column_left/before',
'action' => 'extension/my_ext/events.onAdminMenu'
],
];
foreach ($events as $event) {
$this->model_setting_event->addEvent(array_merge($event, [
'description' => 'My Extension: ' . $event['code'],
'status' => 1,
'sort_order' => 1
]));
}
}
4.5 Создаём собственное событие
Не обязательно только «слушать» встроенные события — можно генерировать свои, чтобы другие разработчики могли подписаться:
<?php
namespace OpencartCatalogControllerExtensionTestModule;
class CustomAction extends OpencartSystemEngineController {
public function index() {
$data = ['message' => 'Кастомное действие выполнено'];
// Генерируем своё событие
$this->event->trigger('extension/test_module/custom_action/after', $data);
}
}
Любой другой модуль зарегистрируется на extension/test_module/custom_action/after так же, как на встроенные события.
4.6 Встроенные точки событий в 4.x
Внутри Loader автоматически вызываются before/after для контроллеров, моделей, представлений, языковых файлов и конфигов:
| Триггер | Когда срабатывает |
|---|---|
controller/*/before / after |
Перед/после выполнения любого контроллера |
model/*/before / after |
Перед/после вызова любого метода модели |
view/*/before / after |
Перед/после рендеринга любого шаблона |
catalog/controller/checkout/cart.add/after |
После добавления товара в корзину |
catalog/model/checkout/order.addOrder/after |
После создания заказа |
catalog/controller/account/login/after |
После входа покупателя |
admin/view/common/column_left/before |
Перед отрисовкой меню админки |
Часть 5. Сравнительная шпаргалка
| OpenCart 2.x | OpenCart 3.x | OpenCart 4.x | |
|---|---|---|---|
| Модель регистрации | extension/event |
setting/event |
setting/event |
| Формат addEvent() | 3 аргумента | 3–5 аргументов | 1 массив с ключами |
| Пример триггера | pre.order.add |
catalog/model/checkout/order/addOrder/before |
catalog/controller/checkout/cart.add/before |
| Классы | без namespace | без namespace | с namespace Opencart... |
| Удаление | deleteEvent($code) |
deleteEventByCode($code) |
deleteEventByCode($code) |
| Параметры (before) | &$route, &$data |
&$route, &$args, &$output |
&$route, &$data, &$output |
| Wildcard-паттерны | нет | нет | да (*, ?) |
| Приоритет | косвенно | sort_order |
sort_order + внутренняя сортировка |
Часть 6. Практические советы
- Уникальный code. Префикс с названием расширения:
myshop_cart_before, неcart_before. Иначе при установке двух модулей от разных разработчиков коды совпадут. - Удаляйте старые события перед повторной регистрацией. В начале
install()вызовитеdeleteEventByCode()передaddEvent()— не будет дубликатов. - Не тяжёлый код в обработчиках. Событие выполняется синхронно при загрузке страницы. Долгий запрос к внешнему API затормозит сайт. Для тяжёлых операций — ставьте задачу в cron.
- Проверяйте isset(). Структура
$argsи$dataотличается в разных методах. Обращайтесь к элементам аккуратно. - Логируйте при отладке.
$this->log->write(print_r($data, true));покажет реальную структуру данных. Логи вsystem/storage/logs/error.log. - Задавайте sort_order явно. Не полагайтесь на порядок по умолчанию — он может измениться при обновлении.
- before vs after. Нужно изменить входные данные →
before. Нужно среагировать на результат →after. - Чистите uninstall(). Мёртвые события в
oc_event— источник ошибок 500 после удаления модуля. - Мультиверсионные расширения. Проверяйте константу
VERSIONи регистрируйте события нужным синтаксисом. Подробнее о различиях OC 3 и 4. - События vs OCMOD. Сначала ищите подходящее событие. Только если его нет — используйте OCMOD.
- Безопасность. Если обработчик получает пользовательский ввод — валидируйте и очищайте. Событие не добавляет автоматической защиты от XSS/SQL-инъекций. Полный чек-лист безопасности OpenCart.
- Тестируйте на стейджинге. Ошибка в обработчике на частом триггере (например,
content_top/before) может «положить» весь фронтенд. - Один обработчик — одна ответственность. Не логируйте, не отправляйте письмо и не меняйте данные в одном методе — разделяйте.
Часть 7. Частые ошибки
- Забыли установить модуль через админку. Просто закинуть файлы в
extension/мало — модуль нужно установить через Расширения → Установщик расширений. Без этогоinstall()не вызовется и события не зарегистрируются. - Опечатка в триггере. OpenCart ищет точное совпадение строки с учётом регистра.
addorder≠addOrder. - Забыли
&(ссылку). Без&$route, &$dataизменения внутри метода потеряются. - Файл-обработчик не в той папке. Событие
catalog/...→ файл вcatalog, событиеadmin/...→ файл вadmin. - Не удаляют события при uninstall.
uninstall()безdeleteEventByCode()— и база полонится мёртвыми записями. - Тяжёлый код в обработчике. Синхронное выполнение — сайт «подвисает» на время обработки.
- Путают формат триггера. В 4.x:
cart.add(точка), а неcart/add(слэш). - Регистрация в index() вместо install(). Десятки дублей в БД при каждом визите на страницу.
- Путают deleteEvent() и deleteEventByCode(). Первый — для OC 2.x, второй — для 3.x/4.x. Неверный метод → фатальная ошибка PHP.
Часть 8. Частые вопросы
В: Можно ли создать своё событие в OpenCart?
О: Да. Вызовите $this->event->trigger(‘ваше/уникальное/имя’, $data) в любом месте кода. С этого момента любой модуль сможет подписаться на это событие через addEvent().
В: Почему событие срабатывает дважды?
О: Чаще всего модуль установлен дважды (переустановлен без удаления) или addEvent() вызывается не только в install(). Проверьте таблицу oc_event на дубликаты по trigger и action.
В: Как узнать имя триггера для нужного действия?
О: Откройте файл ядра и найдите имя метода. Например, addOrder() доступен как catalog/model/checkout/order/addOrder/before и after благодаря автоматической системе в Loader.
В: Обязательно ли создавать видимый модуль в админке?
О: Нет. Можно создать «невидимый» модуль без UI, у которого install() и uninstall() только регистрируют и удаляют события.
В: Работают ли события в cron-скриптах?
О: Да, если CLI-скрипт полноценно инициализирует Registry и подключает Event. Многие простые cron-скрипты урезают инициализацию — проверяйте отдельно.
В: Как временно отключить событие без удаления модуля?
О: В админке: Расширения → События → переключите статус записи на «Выключено». Модуль останется установленным, но событие выполняться не будет.
В: Что лучше: события или OCMOD?
О: События надёжнее для долгосрочной поддержки — они не ломаются при обновлении ядра. OCMOD мощнее (может патчить любой файл), но хрупче. Правило: сначала ищите событие, если его нет — используйте OCMOD.
Часть 9. Итог
- OpenCart «кричит» — вызывает
$this->event->trigger('имя_события', $data). - Вы пишете класс с методом-обработчиком.
- Один раз «подписываетесь» через
addEvent()— обычно вinstall(). - С этого момента OpenCart сам вызывает ваш метод при каждом соответствующем действии — без правки файлов ядра.
- В OC 2.x имена простые (
pre.order.add), в 3.x — путь +/beforeили/after, в 4.x — точка перед именем метода + namespace + wildcard-паттерны. - Всегда: уникальный
code, аккуратная работа с&$data, чистка вuninstall(), минимум тяжёлой логики.
Нужна доработка OpenCart или создание магазина с использованием событий? Свяжитесь с нами — обсудим ваш проект. Также рекомендую ознакомиться с нашей полной статьёй об архитектуре OpenCart 4 и сравнением OpenCart 3 и 4.
Таблица совместимости: какие события доступны в какой версии
Для удобства собрал информацию о доступности ключевых возможностей событий в разных версиях OpenCart:
| Возможность | OpenCart 2.x | OpenCart 3.x | OpenCart 4.x |
|---|---|---|---|
| Базовая система событий | с версии 2.2 | полная | полная + wildcard |
| before/after хуки | ограниченно | полная | полная |
| Wildcard-паттерны (*) | нет | нет | да |
| Пространства имён (namespace) | нет | нет | да |
| Автоматические триггеры для моделей | нет | да | да |
| Автоматические триггеры для view | нет | да | да |
| Регистрация через массив | нет | нет | да |
| Приоритет (sort_order) | косвенно | явно | явно + сортировка |
Если вы планируете миграцию с OpenCart 2.x или 3.x на 4.x, обратите внимание на изменение формата триггеров и регистрационного API. Подробнее о процессе миграции читайте в нашей статье о сравнении версий.
Источники
- Исходный код Event class — OpenCart 4 (GitHub)
- Исходный код Loader class — автоматические before/after триггеры (GitHub)
- Документация событий OpenCart 2.2 (Wiki)
- Официальный сайт OpenCart
Частые вопросы
Как работают события (Events) в OpenCart?
Events позволяют подписаться на хуки (before/after) выполнения функций без модификации файлов ядра.
Чем Events лучше OCMOD?
Events надёжнее: они не ломаются при обновлении ядра. OCMOD мощнее, но хрупче.
Как создать свой event в OpenCart 4?
Вызовите $this->event->trigger() в нужном месте кода. Любой модуль сможет подписаться через addEvent().
Комментарии (0)
Пока нет комментариев. Будьте первым!
Оставить комментарий