
Работа с событиями в OpenCart версии 2.2+ существенно изменилась. С документацией, по этому вопросу, пока плохо. Материал для статьи пришлось брать из своего опыта и разбора кода ядра OpenCart. Поэтому, возможны, неточности.
В OpenCart версии 2.2, события, в основном, не прописаны "жестко" в коде, а генерируются автоматически перед и после выполнения определенного метода определенного контроллера.
События в OpenCart хранятся в
1. В списке событий (массив $_['action_event']) файла конфигурации (system\config\catalog.php или system\config\admin.php для админки)
Тут перечислены служебные события.
2. В базе данных в таблице event. В БД заносятся пользовательские события, например установленные дополнениями (модулями).
Рассмотрим вариант хранения событий в БД.
Вызов проверки наличия обработчиков запускает метод$this->event->trigger(название события, аргументы для действия)
который передает название события, выполняемое в данный момент.
Данный метод, в OC 2.2+ вызывается:
- в классе загрузчика Loader. Файл system\engine\loader.php
Тут метод trigger() вызывается для контроллеров (доступ к которым получаем с помощью загрузчика $this->load->controller()), моделей, видов, языковых файлов. Причем по два раза – до выполнения их кода (before) и после (after). А значит каждый раз, когда, например, в контроллере вызывается какая-то модель через загрузчик:
$this->load->model('account/address');или какой-то языковой файл:
$this->load->language('checkout/cart');и аналогично файл представление или другой контроллер, то выполнение скрипта проходит через класс Event, где сравниваются зарегистрированные обработчики событий с тем событием которое происходит сейчас.
Например в БД , в таблице событий (event) есть поле trigger со значением catalog/controller/product/category/before
Это значит, что перед выполнением контроллера ControllerProductCategory вызовется действие (метод определенного контроллера) которое указано в поле action.
- в классе ControllerStartupRouter. Файл catalog\controller\startup\router.php или аналогичный для админки.
В данном файле, каждый раз при загрузке страницы происходит разбор текущего URL из адресной строки перед выполнением указанного контроллера/метода. Обработчики вызываются так же 2 раза – до выполнения переданных в URL действий
$result = $this->event->trigger('controller/' . $route . '/before', array(&$route, &$data));и аналогичного после.
А название события формируется в этой строке с учетом контроллера/метода из переменной $route, например:
'controller/product/category/before'
или
'controller/product/category/after'
Хоть проверка установленных обработчиков на события запускается именно в этих файлах (loader.php
и router.php), сами события считываются из БД и регистрируются еще до этого в файле catalog\controller\startup\event.php
<?php class ControllerStartupEvent extends Controller { public function index() { // Add events from the DB $this->load->model('extension/event'); $results = $this->model_extension_event->getEvents(); foreach ($results as $result) { $this->event->register(substr($result['trigger'], strpos($result['trigger'], '/') + 1), new Action($result['action'])); } } }где вызывается метод register() объекта Action (system\engine\event.php), сохраняющий их в массиве $data.
Метод trigger(), вызывающий выполнение обработчиков, могут содержать и сторонние дополнения для возможности расширения их функционала.
То есть, в OpenCart 2.2+ названия событий формируются динамически из класс/метод который выполняется, а не прописаны четко в коде. Хотя создавая события в своих модуля, можно и прописать «жестко».
Работа с событиями (добавление/удаление и тд.)
Для работы с событиями в админ-части существует модель admin/model/extension/event.phpДанная модель содержит такие методы:
- addEvent() - для добавления нового Обработчика.
public function addEvent($code, $trigger, $action, $status = 1) { $this->db->query("INSERT INTO `" . DB_PREFIX . "event` SET `code` = '" . $this->db->escape($code) . "', `trigger` = '" . $this->db->escape($trigger) . "', `action` = '" . $this->db->escape($action) . "', `status` = '" . (int)$status . "', `date_added` = now()"); return $this->db->getLastId(); }Метод получает 4 параметра:
$code - код (идентификатор) обработчика, обычно название модуля
$triger - само Событие, которое мы будем обрабатывать
$action - обработчик или метод, который будет вызван для обработки События.
$status – 1 – включено/ 0 -выключено
- deleteEvent() - для удаления Обработчика
а так же методы для получения события или списка событий, включение/выключение отдельного события, удаление события.
Пример работы с событиями в своем модуле:
Файлы модулей могут иметь стандартные методы:
- install() – выполнится при установке (включении) модуля;
- uninstall() – выполнится при удалении (выключении) модуля
в них и нужно работать с событиями, чтобы действия по ним выполнились только 1 раз.
Пример контроллера модуля:
<?php class ControllerExtensionModuleKsl extends Controller { //Сработает только 1 раз при установке public function install() { $this->load->model('extension/event'); $this->model_extension_event->addEvent('ksl-product-cat', 'catalog/controller/product/category/before', 'extension/ksl/my'); } //Сработает только 1 раз при удалении (удалит действие) public function uninstall() { $this->load->model('extension/event'); $this->model_extension_event->deleteEvent('ksl-product-cat'); } }Данное событие сработает перед обработкой текущего URL, выводящего страницу категорий товаров. В результате будет вызван метод index (т.к. не указан другой) контроллера 'extension/ksl/my
Файл catalog\controller\extension\ksl\my.php:
<?php class ControllerExtensionKslMy extends Controller { public function index(&$route, &$data) { $data[0]['city'] = 'Kiev'; /* * То, что возвращает метод вызванный по событию, * заменяет собой то, что возвращает метод который это событие вызвал */ //return $value; } }В зависимости от типа события, своему, выполняемому методу, можно передать несколько аргументов (указаны в файле загрузчика system\engine\loader.php):
- для контроллера (&$route, &$data, &$output);
- для контроллера, при разборе URL в файле router.php (BEFORE) (&$route, &$data);
- для загрузки модели (&$route);
- при вызове отдельного метода модели (&$route, &$args, &$output);
- для вида (&$route, &$data, &$output);
- для языкового файла (&$route, &$output)
$route - путь к файлу вызвавшему событие (для контроллера и модели это путь к их классу, может так-же содержать метод этого класса, который вызывает событие);
$data - аргументы данного метода (массив);
$output – при установке обработчика события на AFTER (после выполнения стандартного метода), с данным аргументом передается результат выполнения этого метода, с которым можно работать (менять). Если установить значение данной переменной в своем коде, установленном на BEFORE, то до выполнения стандартного класса/метода (который вызвал событие) выполнение кода не дойдет, данное значение будет использовано вместо результата работы стандартного класса/метода.
Аргументы $data и $output могут быть не переданы, если имеют значение NULL. Поэтому при передаче аргументов в свой метод, на всякий случай, можно задать им значение по-умолчанию = null, что бы метод отработал:
public function index(&$route, &$data=null, &$output=null) {}
Данные аргументы передают значения по-ссылке, поэтому можно их изменить.
Если изменить значение $route, то можно сделать перенаправление на другой контроллер/метод или просто вернуть результат его выполнения.
Если изменить элементы массива $data, то метод вызвавший событие получит эти измененные данные так же в качестве аргумента.
Если изменить значение $output, то для controller и view, это:
во-первых, предотвратит выполнение самого класса/метода который вызвал данное событие;
во-вторых, данное значение заменит результат, который должен был вернуть этот класс/метод вызвавший данное событие.
Если пользовательский метод, вызванный по событию, что-то возвращает, то для контроллера, вида и метода модели- возвращаемый результат заменит то, что возвращает класс/метод вызвавший данное событие. Это можно использовать для изменения кода самого метода вызывающего события.
Как я уже писал, события можно вешать и на модели, виды, языковые файлы. Примеры есть в списке стандартных событий OC (файл system\config\catalog.php или system\config\admin.php в массиве $_['action_event']):
'model/extension/shipping/*/before' => 'event/compatibility/beforeModel', 'view/shipping/*/before' => 'event/compatibility/view', …тут так же можно заметить, что есть возможность использовать * для подмены пути или названия файла.
Для работы с событиями в frontend существует своя модель - ModelExtensionEvent, файл
catalog\model\extension\event.php
Она имеет всего 1 метод, получающий список событий из таблицы event. Поэтому зарегистрировать событие выполнив какой-то код из директории “Catalog” вы не сможете (по-умолчанию).
Коснемся так же первого пункта - списка служебных событий.
Хоть этот способ и не рассчитывался разработчиками для работы с пользовательскими событиями, в принципе, вызов нужного метода контроллера можно установить на событие, указав его в списке событий конфигурационного файла
system\config\catalog.php
или
system\config\admin.php
в массиве
$_['action_event'] = array(…
Данные списки событий добавляются еще на этапе загрузки библиотек OC в файле system\framework.php:
if ($config->has('action_event')) { foreach ($config->get('action_event') as $key => $value) { $event->register($key, new Action($value)); } }и будут выполнены самыми первыми, в таком же порядке как перечислены.
Например так, можно установить обработчик события для своего дополнения:
'controller/product/category/before' => 'extension/ksl/my',В результате выполнится метод index() контроллера ControllerExtensionKslMy
Можно указать конкретный метод, например get():
'controller/product/category/before' => 'extension/ksl/my/get',Можно использовать звездочку для подмены любого названия:
'controller/product/*/before' => 'extension/ksl/my',
Ознакомиться с примерами работы с событиями разных типов, можно в моей заметке Примеры работы с событиями разных типов в OpenCart 2.2+.
Сам столкнулся с почти полным отсутствием документации на эту тему.
Пришлось также самому разбирать код ядра и экспериментировать.
Система прерываний очень удобный инструмент для разработки дополнений без правки кода.
Думаю, что ваша статья будет очень полезна разработчикам.
ответ на комментарий Александр от 27.04.2017
Я когда хотел изучить события в OpenCart - вообще ничего не нашел подходящего. Немного было про версии 2.0-2.1, но там совсем по-другому. Поэтому пришлось самому разбираться "с нуля".