Услуги Создание магазина Доработка Интеграция 1С О компании FAQ Блог Кейсы Отзывы Контакты
А
Автор статьи

События в 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 ищет точное совпадение строки с учётом регистра. addorderaddOrder.
  • Забыли & (ссылку). Без &$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. Подробнее о процессе миграции читайте в нашей статье о сравнении версий.


Источники

Частые вопросы

Как работают события (Events) в OpenCart?

Events позволяют подписаться на хуки (before/after) выполнения функций без модификации файлов ядра.

Чем Events лучше OCMOD?

Events надёжнее: они не ломаются при обновлении ядра. OCMOD мощнее, но хрупче.

Как создать свой event в OpenCart 4?

Вызовите $this->event->trigger() в нужном месте кода. Любой модуль сможет подписаться через addEvent().

← Предыдущая Почему OpenCart — лучший выбор для бизнеса в 2026 Следующая → Чек-лист проверки сайта на соответствие требованиям РКН: ФЗ-152, cookie, согласия, штрафы

Комментарии (0)

Пока нет комментариев. Будьте первым!

Оставить комментарий

Ваш комментарий появится после проверки модератором.