Событие в 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'); } }
Читала так же эту статью и в ней написано, что модифицировать языковые файлы можно через метод $this->language->set()
http://joxi.ru/krDV6RSEVgWz2p
Буду пробовать на практике сделать что-то с помощью Events основываясь на ваших разъяснениях и статье из блога опенкарт.
Спасибо и удачи в ваших трудах!
И еще один вопрос: а что если в стандартном методе модели, возьмем к примеру admin/model/catalog/review, в методе editReview есть такие строки:
Как в этом случае подменять метод модели? Если ретурнить "первую строку", то вторая же не выполнится? Или как? Очень надеюсь, что понятно изложил вопрос ))))) Спасибо за статью еще раз!
ответ на комментарий Андрей от 01.12.2017
Касательно первого вопроса.
насколько я понял, вы про то, что писать запросы к БД в контроллерах неправильно. Согласен, но в разных фреймворках и CMS свои правила. В OpenCart расширениях (причем стандартных) иногда пишут запросы к БД в контроллерах. Например:admin\controller\extension\payment\pp_express.php
admin\controller\startup\startup.php
они не всегда создают отдельно модель. Тем более, что главный контроллер, от которого наследуются остальные, имеет свойство $db содержащее объект соединения с БД.
Вообще, в OpenCart большинство объектов, включая контроллеры, получают в конструкторе в виде аргумента $registry объект Registry. А данный объект содержит опять же остальные объекты (те же модели). В итоге OpenCart тягает за собой везде глобальный объект со всеми другими объектами. Я об этом писал и это можно увидеть в файле system\framework.php. Короче говоря, MVC тут скорее для вида.
На самом деле вместо:
можно было написать:
Только в таком случае пришлось бы создавать контроллер, который будет вызывать эту модель. А кроме вызова модели вроде как там больше и писать нечего в моем случае. Поэтому я сделал в примере именно так, а в реальном проекте лучше вынести запросы к БД в модель, например:
По второму вопросу.
Конкретно по методу модели который вы привели в пример (admin/model/catalog/review.php), а это у нас нажатие кнопки сохранения при редактировании отзывов в каталоге в админке.Ничего «ретурнить» в нем не надо. Вешаем вызов своего метода на событие
В итоге выполнится наш метод указанный при создании обработчика события. Он может иметь вид:
Т.е. для работы метода нужно вытащить ему аргументы из массива $args, а далее переопределяйте как хотите. Оригинальный код метода при этом не выполнится, т.к. использовано событие «befor» и переопределенный метод возвращает значение которое можно интерпретировать как true (специально для этого я и прописал).
Если бы оригинальный метод что-то возвращал, то можно было бы повесить на «after» и не переопределять код метода, а просто изменить возвращаемый результат - $output.