В предыдущей статье, я описал общий процесс работы с событиями - как они формируются, что собой представляет обработчик события, как привязать выполнение метода своего класса к определенному событию и тд. Сейчас углубимся в их практическое применение.

Событие в OpenCart 2.2+ вызывается подключением одного из файлов (контроллера, модели, вида, языкового файла) с помощью загрузчика – экземпляра класса Loader. Например:
$this->load->controller('common/header');
Класс загрузчика находится в файле system\engine\loader.php где можно посмотреть многое из того, о чем пойдет речь в этой статье.
Так же событие генерируется в файле catalog\controller\startup\router.php или аналогичном для админки, где происходит разбор текущего URL из адресной строки перед выполнением указанного в URL контроллера/метода.

Вызов события осуществляется методом trigger() 2 раза на одно событие – перед ним и после. Таким образом можно привязать выполнение своего класса/метода как до выполнения этого события (например вы хотите подменить аргументы с которыми будет работать стандартный метод, вызвавший это событие или же вы хотите вовсе отменить загрузку стандартного события и подключить свой файл или свой метод вместо него) так и после (например вам нужно получить результат работы стандартного метода или результат загрузки файла, обработать эти данные и вернуть уже обновленные). Вариантов есть много.

В зависимости от типа события, работать с ними нужно по-разному. Самый простой вариант, если вам, при наступлении какого-то события (например загрузка шапки сайта) нужно просто выполнить свой код. Тогда принцип работы везде одинаков. Но если вам надо как-то обрабатывать данные с которыми работает событие или результат его работы – способы работы будут отличаться. Так, к примеру, событие вызывающее контроллер работает с методом этого контроллера, который нужно загрузить и выполнить с возможными аргументами этого метода. С событиями работающими с моделью еще сложнее, т.к. при загрузке модели никакие ее методы не выполняются:
$this->load->model('catalog/category'); 
но они сохраняются для дальнейшего вызова и выполнения, вызывая дополнительные события уже при выполнении каждого отдельного метода. Событие работающее с языковым файлом работает только с загрузкой файла и сохранения массива из него и тд. Вот о таких нюансах и поговорим.

1. Контроллер.

Для контроллера, обработчик вешается всегда на определенный метод класса контроллера. Даже если он не прописан (common/column_left) и отсутствует в URL (в параметре route), обработчик вешается на метод по-умолчанию (index).

При создании своего класса, метод которого должен выполниться при наступлении определенного события (вызова контроллера) в этот метод передается по-ссылке аргумент $route, который и содержит путь к исполняемому методу, вызвавшему событие:
product/category/validate
тут это метод validate(), если же метод не указан:
product/category
значит имеется ввиду метод по-умолчанию – index(). Метод index, разработчики включили почти во все служебные классы, так что такая краткая запись применяется очень часто.

Пример вызова контроллера 'common/column_left' из метода основного контроллера и сохранение результата в массив:
$data['column_left'] = $this->load->controller('common/column_left');
При наступлении события, в данном случае
catalog/controller/common/column_left/before
значение $route (common/column_left) передается в свой метод (по ссылке), который указан в обработчике для выполнения по событию, в качестве первого аргумента.
Так же, вторым аргументом, тоже по-ссылке, передается параметр $data – аргументы метода, если они есть. Таким образом есть возможность подмены аргументов для основного метода контроллера, вызвавшего событие – назначили обработчик на before (перед выполнением основного метода контроллера), изменили параметр $data и основной метод контроллера выполнится уже с измененными аргументами.
Про третий аргумент $output написано ниже.

C помощью оператора return, в своем методе, можно вернуть результат, который заменит результат работы того метода, класс которого вызвал событие. Т.к. если return = true (какие-то данные), то загрузка контроллера, который вызвал событие, не начинается.
<?php
class ControllerExtensionKslMy extends Controller {    
    public function index(&$route, &$data=null, &$output=null) {    
        $date = 'текст боковой колонки';    
        return $date; //если нужно заменить результат выполнения метода
    }
}

Если нужно поменять результат выполнения стандартного метода контроллера, которое вызывает событие, то нужно обработчик вешать на AFTER и в коде своего метода менять полученный аргумент $output, который и содержит этот результат:
<?php
class ControllerExtensionKslMy extends Controller {    
    public function index(&$route, &$data=null, &$output=null) {    
        $output = 'текст боковой колонки';    
    }
}




Модель.

При загрузке самой модели, например
$this->load->model('account/address');
в аргумент $route своего класса/метода повешенного обработчиком на определенное событие (загрузку какой-то модели) не передается метод, который будет выполняться, а только класс всей модели.
Поэтому, если нужно событие – загрузка всей модели – ставить в обработчике событие так:
catalog/model/extension/extension/before

Далее в файле system\engine\loader.php в цикле перебираются все методы данный модели и передаются в метод callback(), уже с переменной $route содержащей и класс модели и выполняемый метод. Тут для каждого из этих методов так же создается событие перед и после его выполнения. Поэтому есть возможность повесить обработчик на событие, в котором указан метод класса модели, при выполнении которого событие должно сработать. Например
catalog/model/localisation/language/getLanguages/before
где getLanguages – метод данной модели.

Если нужно поменять код самого метода, то лучше вешать обработчик перед выполнением метода стандартной модели (before), тогда, выполнится только свой метод, а до выполнения метода на который повесили обработчик дело не дойдет.
Стоит заметить, что для того, чтобы метод на который повесили обработчик не был выполнен, метод который его переопределил должен вернуть что-то соответствующее значению true, иначе выполнится и свой метод и метод на который повесили обработчик события. Таким образом, для отмены метода модели нужно вернуть true или другое соответствующее ему значение:
return true;

Пример своего класса, выполняемого в случае наступления события - изменение всего метода модели (обработчик повешен на событие catalog/model/extension/extension/getExtensions/before):

<?php
class ControllerExtensionKslMy extends Controller {    
    public function index(&$route, &$args, &$output) {
        //Аргументы передаются в виде массива, поэтому нужно их извлечь
        $type = $args[0];
        $query = $this->db->query("SELECT * FROM " . DB_PREFIX . "extension WHERE `type` = '" . $this->db->escape($type) . "'");
        return $query->rows;
    }
}

Если нужно поменять результат выполнения стандартного метода, которое вызывает событие, то нужно вешать на AFTER и в коде своего метода менять полученный аргумент $output, который и содержит этот результат.

Файл представление (вид).

Например событие вызывающее файл представление (вид):
catalog/view/common/column_left/before
Обработчик к нему может содержать вызов своего метода такого плана:
<?php
class ControllerExtensionKslMy extends Controller {    
    public function index(&$route, &$args, &$output=null) {

        //Заменяем файл представление своим файлом
        return $this->load->view('common/ksl', $args);

        //Если контента немного, можно поместить в переменную прямо в своем методе
        $content = 'нужный HTML код';
        return $content;
    }
}
аргументы, которые передаются в вид, содержатся в массиве $args. Можем изменить что-то из них прямо тут в методе до передачи.

Языковой файл.

Например событие вызывающее языковой файл (подвал сайта)
catalog/language/common/footer/before
В обработчик события, передается только 2 аргумента
$route – путь к файлу
$output – массив, который возвращает языковой файл

Было бы здорово, если бы было можно загрузить стандартный языковой файл, а затем изменить в нем какие-то значение на свои. Собственно аргумент $output (передается по ссылке) предназначен как раз для того, чтобы установив событие после загрузки языкового файла (в данном случае было бы catalog/language/common/footer/after), изменить некоторые значения данного аргумента, который должен содержать уже загруженный массив языкового файла. Но, по крайней мере в версии OpenCart 2.3, изменение массива $output ни на что не повлияет. Видимо какой-то баг, не исправленный на данный момент.

В результате не имеем возможности изменить частично языковой файл и можем только подключить целиком другой, изменив путь к языковому файлу с помощью аргумента $route, задав там путь к своему файлу.
Например
<?php
class ControllerExtensionKslMy extends Controller {    
    public function index(&$route,&$output) {
    $route = 'common/ksl';
    }
}
в данном случае подключится не файл common/footer, а свой файл, расположенный в common/ksl.

Так же есть возможность не менять языковой файл, а подключить свой дополнительно. При этом, совпадающие элементы массива языковых файлов перезапишутся более новым элементом из своего файла. Для этого нужно установить обработчик после срабатывания события (AFTER). Например на событие catalog/language/common/footer/after
Свой класс, вызываемый при наступлении события, будет выглядеть таким образом:
<?php
class ControllerExtensionKslMy extends Controller {    
    public function index(&$route,&$output) {
    $this->load->language('common/ksl');
    }
}