Статьи и новости
Версия LiveStore обновилась до 3.0.4.2

Версия LiveStore обновилась до 3.0.4.2, что нового?

 
 
 
 
 
Версия LiveStore обновилась до 3.0.4.1

Версия LiveStore обновилась до 3.0.4.1, что нового?

 
 
 
 
 
Подборка новых модулей для Opencart за сентябрь 2025

Новинки за сентябрь 2025 года: Уведомления в Мах, Интеграция с Bartender, публикация товаров в Телеграм, Пункты выдачи товаров и 5 других модулей.

 
 
 
 
 
Самые продаваемые в сентябре 2025 года шаблоны и дополнения

Самые продаваемые в сентябре 2025 года шаблоны и дополнения: Поиск с морфологией + Sphinx, Водяные знаки, Микроразметка, шаблон Техникс.

 
 
 
 
 

Как правильно создавать модули для OpenCart 3

 
Как правильно создавать модули для OpenCart 3

Модуль для OpenCart 3 - это не только рабочий функционал, но и грамотный подход к коду и структуре. Непродуманный код может отпугнуть покупателей, а правильный стиль разработки повышает доверие и облегчает работу с вашим продуктом. Ниже будет несколько советов, возможно они помогут вам в разработке. Если вы хотите что-то добавить или изменить в тексте - пишите в комментариях или на почту.

Стиль написания кода модуля

Этот раздел задает общий подход к оформлению кода и единые ожидания для команды. Простые правила повышают читаемость и снижают стоимость поддержки.

Единый синтаксис

Здесь собраны короткие и проверяемые правила форматирования и именования. Они помогают держать код в одном стиле.

  • Отступы - используйте табуляцию, избегайте пробелов. Не смешивайте табы и пробелы в одном файле
  • Фигурные скобки - ставьте открывающую на той же строке, что и конструкция (if, foreach, function).
  • Пробелы - добавляйте пробелы вокруг операторов (=, ==, +, .), после запятых.
  • Именование - классы CamelCase, переменные и ключи массивов snake_case.
class ControllerExtensionModuleLiveExample extends Controller {
    public function index() {
        $this->load->language('extension/module/live_example');
        
        $data['example_status'] = $this->config->get('module_live_example_status');
        
        if ($data['example_status']) {
            return $this->load->view('extension/module/live_example', $data);
        }
    }
}

Структура кода

Определяем где хранить логику, данные и представление. Четкая структура делает проект предсказуемым и удобным для доработок.

  • Контроллеры - тонкие, вся бизнес-логика должна быть в моделях.
  • Валидация и проверки - выполняются в модели, а не в шаблонах.
  • Языковые файлы - никакого текста в коде и шаблонах, всё в language/.
  • Twig-шаблоны - используйте нативный синтаксис ({{ variable }}, {% if %}), не PHP.

Разделение обязанностей: модель, контроллер, библиотека

Обозначаем роли основных слоев. Четкие границы упрощают тестирование и повторное использование кода.

Модель

Задача: работа с базой данных, SQL-запросы и возврат данных.

public function getCustomerByEmail($email) {
    $sql = "SELECT * FROM " . DB_PREFIX . "customer WHERE email = '" . $this->db->escape($email) . "'";
    $query = $this->db->query($sql);
    return $query->row;
}

Контроллер

Задача: работа с $this->request, вызов моделей, формирование $this->response. В контроллере не должно быть SQL и сложной бизнес-логики.

public function getCustomer() {
    $this->load->model('account/customer');

    $email = $this->request->get['email'];
    $customer = $this->model_account_customer->getCustomerByEmail($email);

    $this->response->setOutput(json_encode($customer));
}

Библиотека

Задача: хранить бизнес-логику модуля, которая может использоваться из разных мест (фронт, админка, крон).

class Bonus {
    public function calculate($order_total) {
        return floor($order_total * 0.05);
    }
}

Преимущества разделения

  • Поддерживаемость - изменения проще вносить: SQL в моделях, логика в библиотеках, ответы в контроллерах.
  • Тестируемость - можно тестировать отдельно модель (SQL), библиотеку (логика), контроллер (API).
  • Повторное использование - библиотеки можно подключать во фронте, в админке и в CRON.

Совместимость и безопасность

Собираем базовые практики защиты и проверки совместимости. Эти шаги снижают риски и продлевают жизнь коду.

  • Используйте $this->db->escape() и (int) для SQL-запросов.
  • Применяйте htmlspecialchars или html_entity_decode при выводе.
  • Избегайте устаревших функций (mysql_*, ereg, split).
  • Для строк используйте mb_*.
  • Проверяйте работу на актуальных версиях PHP (7.4, 8.0, 8.1, 8.2).

Экранирование входных данных

Здесь показано как безопасно принимать данные и готовить запросы. Примеры можно сразу вставить в рабочий код.

Неправильно (параметры из request прямо в SQL):

$query = $this->db->query("SELECT * FROM " . DB_PREFIX . "customer WHERE email = '" . $this->request->post['email'] . "'");

Если злоумышленник введет ' OR 1=1 --, запрос вернет всех пользователей.

Правильно (контроллер валидирует, модель работает с аргументами):

Контроллер:

public function info() {
    // 1) получаем и валидируем email
    $email = trim($this->request->post['email'] ?? '');
    if (!filter_var($email, FILTER_VALIDATE_EMAIL) || mb_strlen($email) > 96) {
        $this->response->addHeader('Content-Type: application/json');
        $this->response->setOutput(json_encode(['error' => 'Некорректный email']));
        return;
    }

    // 2) работаем через модель
    $this->load->model('account/customer');
    $customer = $this->model_account_customer->getCustomerByEmail($email);

    $this->response->addHeader('Content-Type: application/json');
    $this->response->setOutput(json_encode($customer));
}

Модель:

public function getCustomerByEmail($email) {
    $email = $this->db->escape($email);

    $query = $this->db->query(
        "SELECT * FROM " . DB_PREFIX . "customer WHERE email = '" . $email . "'"
    );

    return $query->row;
}

Для числовых значений используйте явное приведение и простую валидацию:

Контроллер:

$order_id = (int)($this->request->get['order_id'] ?? 0);
if ($order_id <= 0) {
    $this->response->addHeader('Content-Type: application/json');
    $this->response->setOutput(json_encode(['error' => 'Некорректный order_id']));
    return;
}

$this->load->model('account/order');
$order_info = $this->model_account_order->getOrder($order_id);

$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode($order_info));

Модель:

public function getOrder($order_id) {
    $query = $this->db->query(
        "SELECT * FROM " . DB_PREFIX . "order WHERE order_id = " . (int)$order_id
    );
    return $query->row;
}

Золотое правило: строки через $this->db->escape(), числа через (int) или (float). Не используйте $this->request внутри модели.

Разделение SQL-запроса построчно

Показываем как оформлять запросы так, чтобы их было удобно читать и расширять. Единый стиль ускоряет ревью и снижает число ошибок.

Для коротких запросов можно использовать одну строку:

$query = $this->db->query("SELECT * FROM " . DB_PREFIX . "customer WHERE customer_id = " . (int)$customer_id);

Но для длинных запросов рекомендуется многострочный формат:

$query = $this->db->query("
    SELECT p.product_id, p.model, pd.name, p.price 
    FROM " . DB_PREFIX . "product p
    LEFT JOIN " . DB_PREFIX . "product_description pd ON (p.product_id = pd.product_id)
    LEFT JOIN " . DB_PREFIX . "product_to_category p2c ON (p.product_id = p2c.product_id)
    WHERE pd.language_id = " . (int)$this->config->get('config_language_id') . "
      AND p.status = '1'
      AND p.date_available <= NOW()
    ORDER BY p.date_added DESC
    LIMIT 20
");
  • Четко видно структуру запроса: SELECT, FROM, JOIN, WHERE, ORDER BY.
  • Легче добавлять новые условия без потери читаемости.
  • Проще находить ошибки SQL.
  • Единый стиль для всех разработчиков в команде.

Подключение собственных библиотек

Разбираем варианты подключения своих классов. Выбор зависит от того, где нужен объект и когда его лучше инициализировать.

1. Через реестр в catalog/startup/startup.php

Подходит, если библиотека должна быть доступна глобально на фронте сразу после старта.

  1. Положите файл библиотеки в system/library/MyLib.php или в catalog/library/MyLib.php.
  2. Внутри catalog/controller/startup/startup.php добавьте регистрацию:
// Если библиотека в system/library:
// $this->load->library('mylib');
// $this->registry->set('mylib', $this->mylib);

// Если библиотека в catalog/library:
require_once(modification(DIR_CATALOG . 'library/mylib.php'));
$this->registry->set('mylib', new MyLib($this->registry));

Плюсы: единая точка инициализации, доступность везде. Минусы: объект создается на каждом запросе, даже если не используется.

2. Локально через require modification(...) в месте использования

Подходит для ленивого подключения только там, где реально нужно.

require_once(modification(DIR_SYSTEM . 'library/mylib.php'));

$mylib = new MyLib($this->registry);
$result = $mylib->doWork($params);

Плюсы: подключение по требованию. Минусы: подключение придется повторять или вынести в helper.

3. Через загрузчик OpenCart: $this->load->library()

Стандартный способ, если библиотека находится в system/library.

$this->load->library('mylib');      // ищет system/library/mylib.php
$this->mylib->doWork($params);      // объект доступен как $this->mylib

Рекомендация: если библиотека лежит в system/library - используйте $this->load->library(). Если нужна ранняя глобальная инициализация - регистрация в startup.php. Если библиотека в другом каталоге - подключайте через require modification(...).

Комментарии

Комментарии дополняют код и поясняют намерение. Короткие и точные описания экономят время на чтение.

/**
 * Получение списка товаров для примера
 *
 * @param int $limit Количество товаров
 * @return array Список товаров
 */
public function getProducts($limit = 10) {
    $query = $this->db->query("SELECT * FROM " . DB_PREFIX . "product LIMIT " . (int)$limit);
    return $query->rows;
}

Дайте возможность покупателю видеть версию вашего модуля

Версия служит ориентиром для обновлений и поддержки. Прозрачность уменьшает количество обращений в техподдержку.

Показывайте номер версии в настройках модуля. Это упрощает поддержку и исключает путаницу.

  1. В контроллере админки сохраните номер версии и передайте его в шаблон:
class ControllerExtensionModuleMyModule extends Controller {
    private $module_version = '1.0.0';

    public function index() {
        // ... ваша логика
        $data['module_version'] = $this->module_version;
        return $this->load->view('extension/module/my_module', $data);
    }
}
  1. В шаблоне админки выведите значение:
<div class="text-muted">Версия модуля: {{ module_version }}</div>

Совет: номер версии должен совпадать с версией в архиве и в CHANGELOG, чтобы клиент всегда видел актуальную информацию.

Хорошо сделанный модуль для OpenCart 3 - это чистый и структурированный код с единым стилем.

Это снижает количество вопросов, увеличивает доверие и помогает продавать модуль эффективнее.


Рекомендуем прочитать