Свой модуль в OpenCart 3.x: рабочие приёмы без боли
Разбираем разработку собственного модуля для OpenCart 3.0.x: структуру файлов, настройки в админке, вывод через layout, обработку данных и ошибки, из-за которых расширение ломается после установки.

Собственный модуль в OpenCart 3.0.x можно собрать без правки ядра: контроллер принимает настройки, Twig-шаблон выводит блок, а layout связывает его с нужной страницей. На минимальную рабочую версию обычно уходит несколько часов, но основная цена ошибки появляется позже — после обновления темы, установки другого расширения или переноса сайта.
Сначала выберите способ подключения
В OpenCart модуль — это не один PHP-файл. Для администраторской части, витрины, языка и шаблона нужны разные каталоги. Начинать стоит с архитектуры, иначе код быстро превращается в набор условий внутри контроллера.
| Подход | Когда использовать | Цена решения |
|---|---|---|
| Обычный модуль через layout | Нужно вывести самостоятельный блок в колонке, контентной области или футере | Проще установить и поддерживать, но позиция зависит от layout |
| Модуль с настройками | Администратор должен менять текст, статус, ссылку или изображение | Появляются форма, валидация и сохранение настроек |
| Событие OpenCart | Нужно изменить данные или поведение существующего контроллера | Меньше правок темы, но сложнее отлаживать порядок срабатывания |
| Правка ядра или файла темы | Только как временный эксперимент | Быстрый результат, но обновление может стереть изменения |
Хороший модуль добавляет поведение рядом с ядром, а не переписывает его. Если расширение нельзя удалить без ручного поиска по десяткам файлов, оно уже дорого в сопровождении.
Сноска. Layout в OpenCart — схема размещения блоков на страницах магазина. В ней модулю назначают маршрут, позицию и порядок вывода.
Базовая структура модуля
Ниже — каркас для OpenCart 3.0.x. Назовём модуль stock_note: он выводит короткое сообщение о наличии товара. Префикс нужен не для красоты: одинаковые имена настроек у разных расширений могут перезаписывать друг друга.
admin/controller/extension/module/stock_note.php
admin/language/ru-ru/extension/module/stock_note.php
admin/view/template/extension/module/stock_note.twig
catalog/controller/extension/module/stock_note.php
catalog/language/ru-ru/extension/module/stock_note.php
catalog/view/theme/default/template/extension/module/stock_note.twig
Для production-поставки эти файлы обычно собирают в архив расширения с корректными путями внутри него. Не копируйте контроллер в каталог темы: OpenCart ищет маршрут модуля в каталоге контроллеров, а не по имени шаблона.
Приём 1. Отделяйте настройки от вывода
Администраторская часть должна только показать форму, проверить данные и сохранить их. Витрина читает настройки через конфигурацию и готовит данные для Twig. Такой разрыв позволяет менять внешний вид без доступа к панели управления.
Настройки в контроллере администратора
Фрагмент ниже рассчитан на OpenCart 3.0.x и показывает ключевую часть метода index(). Редирект и общие элементы формы нужно добавить по стандарту вашей сборки.
$this->load->language('extension/module/stock_note');
$this->load->model('setting/setting');
if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validate()) {
$this->model_setting_setting->editSetting('module_stock_note', $this->request->post);
$this->session->data['success'] = $this->language->get('text_success');
$this->response->redirect($this->url->link(
'marketplace/extension',
'user_token=' . $this->session->data['user_token'] . '&type=module',
true
));
}
$data['module_stock_note_text'] = $this->config->get('module_stock_note_text');
$data['module_stock_note_status'] = $this->config->get('module_stock_note_status');
$data['action'] = $this->url->link(
'extension/module/stock_note',
'user_token=' . $this->session->data['user_token'],
true
);
Ключ module_stock_note становится префиксом всех параметров модуля. В HTML-форме названия полей должны совпадать с ключами POST, например module_stock_note_text и module_stock_note_status.
Проверяйте права и входные данные
Метод validate() должен проверять разрешение пользователя на изменение настроек. Для текстового поля задайте ограничение длины и уберите значение, которое не нужно сохранять. Не полагайтесь только на JavaScript: запрос к административному контроллеру можно отправить напрямую.
protected function validate() {
if (!$this->user->hasPermission('modify', 'extension/module/stock_note')) {
$this->error['warning'] = $this->language->get('error_permission');
}
if (isset($this->request->post['module_stock_note_text'])) {
$text = trim($this->request->post['module_stock_note_text']);
if (utf8_strlen($text) > 300) {
$this->error['warning'] = $this->language->get('error_text');
}
}
return !$this->error;
}
Проверка результата: после сохранения обновите страницу настроек и убедитесь, что введённый текст не исчез, а в таблице модулей статус отображается корректно.
Приём 2. Контроллер витрины должен быть тонким
Контроллер модуля не должен строить HTML и не должен содержать SQL-запросы. Его задача — получить конфигурацию, подготовить небольшой массив данных и передать его шаблону.
class ControllerExtensionModuleStockNote extends Controller {
public function index($setting) {
if (!$this->config->get('module_stock_note_status')) {
return '';
}
$this->load->language('extension/module/stock_note');
$data['text'] = $this->config->get('module_stock_note_text');
$data['heading_title'] = $this->language->get('heading_title');
return $this->load->view(
'extension/module/stock_note',
$data
);
}
}
В шаблоне используйте переменные Twig, а не конкатенацию PHP. Обычный вывод должен экранироваться автоматически. Не добавляйте фильтр raw, если значение не прошло отдельную проверку и действительно должно содержать разрешённый HTML.
<div class="stock-note">
<strong>{{ heading_title }}</strong>
<p>{{ text }}</p>
</div>
Проверка результата: откройте страницу, где модуль назначен в layout. При выключенном статусе блок не должен появляться, при включённом — текст должен выводиться без HTML-инъекций.
Приём 3. Назначайте модуль через layout, а не через тему
После установки модуль нужно связать с маршрутом страницы. В административной части создайте или откройте layout, укажите маршрут, выберите позицию и добавьте модуль. Например, для карточки товара маршрут обычно связан с каталогом товара, а для главной страницы — с маршрутом home в конкретной конфигурации магазина.
Учитывайте три настройки, которые часто путают:
- Статус модуля разрешает или запрещает его работу вообще.
- Layout определяет, на каких маршрутах блок разрешён.
- Позиция задаёт место вывода: контент, колонка, футер или другая область темы.
Если блок виден на одной странице, но не на другой, сначала проверяйте layout и маршрут. Если блок не виден нигде, смотрите статус, права на файл контроллера, журнал ошибок и совпадение имени шаблона с путём в $this->load->view().
Что ломается чаще всего
Модуль установлен, но его нет в списке
В OpenCart 3.0.x файл контроллера должен находиться по маршруту extension/module/stock_note. Проверьте регистр букв, имя класса ControllerExtensionModuleStockNote и наличие файла в admin/controller/extension/module/. После копирования файлов очистите кеш модификаторов и темы, если они используются в конкретной сборке.
Ошибка «Permission denied» в админке
У пользователя нет разрешения на доступ или изменение маршрута. Добавьте права на маршрут модуля в группе пользователя и повторите вход. Не отключайте проверку hasPermission(): так любой пользователь с доступом к административному URL сможет менять настройки.
Пустой блок на витрине
Проверьте, что контроллер возвращает строку, а шаблон лежит в каталоге активной темы. В OpenCart путь к шаблону начинается с extension/module/stock_note, но физический файл находится внутри catalog/view/theme/.../template/.
Старое содержимое продолжает отображаться
Очистите кеш темы и модификаторов, затем проверьте результат в приватном окне браузера. Если используется VQMod, OCMOD или сторонний кеш, очистка выполняется средствами конкретного расширения.
Форма сохраняет не все поля
Сравните три имени: атрибут name в форме, ключ в $this->request->post и параметр, который читает $this->config->get(). Одной лишней буквы достаточно, чтобы значение молча стало пустым.
Когда вместо модуля нужен event
Если задача звучит как «добавить отдельный блок», обычный модуль почти всегда проще. Событие стоит выбирать, когда нужно изменить уже подготовленные данные или добавить логику в существующий маршрут без копирования контроллера темы.
У событий есть своя цена: нужно зарегистрировать обработчик, точно знать точку срабатывания и учитывать конфликт с другими расширениями. При проблеме пользователь видит итоговый HTML, но не всегда очевидно, какой обработчик его изменил. События и инвалидация кеша — отдельная тема; здесь важна граница: не используйте event только потому, что он кажется более «правильным».
Что делать дальше
Сначала установите модуль на копию магазина OpenCart 3.0.x и проверьте четыре сценария: включение, выключение, пустое значение и ввод длинной строки. Затем назначьте его на один layout, включите журнал ошибок и протестируйте страницу без авторизации.
После этого добавьте файл установки и удаления расширения, если модулю нужны отдельные таблицы или события. В production следите за временем ответа страницы, количеством SQL-запросов и журналом ошибок после очистки кеша. На OpenCart 4.x эту структуру переносить без проверки нельзя: там изменились требования к упаковке и размещению расширений, поэтому перед миграцией сверяйтесь с документацией именно вашей версии.
Создатель презентаций: Структура и текст каждого слайда: заголовки-утверждения, одна мысль на слайд, заметки докладчику.
Спросить за 150 ₽Источники: OpenCart Documentation, OpenCart на GitHub
Частые вопросы
Подойдёт ли этот пример для OpenCart 4.x?
Почему модуль не появляется после копирования файлов?
Можно ли хранить настройки модуля в своей таблице?
Что делать, если блок работает в теме по умолчанию, но не работает в моей теме?
Можно ли вставлять HTML, введённый администратором?
Нужно ли править файлы ядра ради собственного модуля?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.