↩️ Назад

Категории

Как создать свой скоростной плагин магазина для Joomla типо VirtueMart для 1000 товаров

19.10.2026 | из категории: Joomla

Joomla · Разработка

Как создать свой плагин для Joomla типо VirtueMart, только не тормозной для 1000 товаров

Пишем свой лёгкий магазин на нативном MVC Joomla 5. Без F0F, без ORM-прослоек, без «комбайнов». Только то, что реально нужно для каталога на тысячу SKU.

📅 19 октября 2026 ⏱ 12 мин чтения 🏷 Joomla 5, PHP 8.2, MySQL

Почему VirtueMart и HikaShop тормозят

Когда я смотрю на VirtueMart или HikaShop, у меня возникает смешанное чувство. С одной стороны — это гигантские комбайны, которые решают всё. С другой — для магазина на 1000 товаров их архитектура часто оказывается избыточной. Ты платишь за это скоростью админки, весом запросов и сложностью кастомизации.

Основные причины:

Идеология: компонент, а не плагин

Сразу оговорюсь: то, что мы делаем — это компонент, а не плагин в чистом виде Joomla. Плагин в Joomla — это обработчик событий. Магазин — это сложная логика с моделями, контроллерами и представлениями. Поэтому мы делаем компонент, но с минималистичной архитектурой.

Ключевая идея — не тащить в Joomla лишние абстракции. Мы будем использовать нативный MVC Joomla 5 и прямые запросы к БД через DatabaseDriver. Никаких прослоек вроде F0F или тяжёлых ORM.

Принцип: одна таблица товаров, одна таблица заказов, нативные контроллеры и модели Joomla. Всё остальное — по мере необходимости, а не «на всякий случай».

Чеклист старта проекта

Структура файлов

Вот как выглядит дерево компонента. Это стандартная раскладка Joomla 5: всё, что относится к админке — в admin/, всё, что к фронтенду — в site/.

com_shop/
├── shop.xml                        # манифест установки
├── admin/
│   ├── access.xml                    # права доступа
│   ├── config.xml                    # конфиг компонента
│   ├── sql/
│   │   ├── install.mysql.sql
│   │   └── uninstall.mysql.sql
│   ├── src/
│   │   ├── Controller/
│   │   │   └── ProductsController.php
│   │   ├── Model/
│   │   │   ├── ProductsModel.php
│   │   │   └── ProductModel.php
│   │   ├── Table/
│   │   │   └── ProductTable.php
│   │   └── View/
│   │       └── Products/
│   │           └── HtmlView.php
│   └── tmpl/
│       └── products/
│           └── default.php
└── site/
    ├── src/
    │   ├── Controller/
    │   │   ├── DisplayController.php
    │   │   └── CartController.php
    │   ├── Model/
    │   │   └── CatalogModel.php
    │   └── View/
    │       └── Catalog/
    │           └── HtmlView.php
    └── tmpl/
        └── catalog/
            └── default.php

Манифест shop.xml

Этот файл говорит Joomla, что мы устанавливаем. Обрати внимание на <namespace> — без него Joomla 5 не поймёт, где искать твои классы.

<?xml version="1.0" encoding="utf-8"?>
<extension type="component" method="upgrade">
    <name>COM_SHOP</name>
    <creationDate>2026-10-19</creationDate>
    <version>0.1.0</version>
    <description>Лёгкий магазин для 1000 товаров</description>

    <!-- Критически важно для Joomla 5 -->
    <namespace path="src">MyCompany\Component\Shop</namespace>

    <install>
        <sql>
            <file driver="mysql" charset="utf8">sql/install.mysql.sql</file>
        </sql>
    </install>

    <uninstall>
        <sql>
            <file driver="mysql" charset="utf8">sql/uninstall.mysql.sql</file>
        </sql>
    </uninstall>

    <administration>
        <menu img="class:cart">COM_SHOP</menu>
        <files folder="admin">
            <folder>services</folder>
            <folder>src</folder>
            <folder>sql</folder>
            <folder>tmpl</folder>
            <filename>access.xml</filename>
            <filename>config.xml</filename>
        </files>
    </administration>

    <files folder="site">
        <folder>src</folder>
        <folder>tmpl</folder>
    </files>
</extension>
Важно: в Joomla 5 без секции <namespace> компонент просто не загрузится. Это самая частая ошибка при портировании старых расширений.

SQL-схема БД

Создаём таблицу товаров. Минимум полей для скорости — расширять будем по мере необходимости.

CREATE TABLE IF NOT EXISTS `#__shop_products` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `alias` varchar(255) NOT NULL,
  `price` decimal(10,2) NOT NULL DEFAULT '0.00',
  `sku` varchar(100) DEFAULT NULL,
  `stock` int(11) NOT NULL DEFAULT '0',
  `state` tinyint(1) NOT NULL DEFAULT '1',
  `catid` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `idx_state` (`state`),
  KEY `idx_catid` (`catid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Индексы по state и catid — это то, что чаще всего используется в WHERE. Для 1000 товаров этого достаточно, чтобы выборка была мгновенной.

Модель товаров (админка)

Здесь мы показываем, как достать 1000 товаров без тормозов. Используем нативный ListModel и getListQuery.

<?php
namespace MyCompany\Component\Shop\Administrator\Model;

use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\ParameterType;

class ProductsModel extends ListModel
{
    public function __construct($config = [])
    {
        if (empty($config['filter_fields'])) {
            $config['filter_fields'] = [
                'id', 'a.id',
                'title', 'a.title',
                'price', 'a.price',
                'state', 'a.state'
            ];
        }
        parent::__construct($config);
    }

    protected function getListQuery()
    {
        $db = $this->getDatabase();
        $query = $db->getQuery(true);

        // Выбираем только нужные поля
        $query->select([
                $db->quoteName(['a.id', 'a.title', 'a.price', 'a.sku', 'a.stock', 'a.state'])
            ])
            ->from($db->quoteName('#__shop_products', 'a'));

        // Фильтр по статусу (публикация)
        $state = $this->getState('filter.state');
        if (is_numeric($state)) {
            $query->where($db->quoteName('a.state') . ' = :state')
                  ->bind(':state', $state, ParameterType::INTEGER);
        }

        // Поиск по названию
        $search = $this->getState('filter.search');
        if (!empty($search)) {
            $search = '%' . str_replace(' ', '%', trim($search)) . '%';
            $query->where($db->quoteName('a.title') . ' LIKE :search')
                  ->bind(':search', $search);
        }

        // Сортировка
        $orderCol = $this->state->get('list.ordering', 'a.id');
        $orderDirn = $this->state->get('list.direction', 'DESC');
        $query->order($db->escape($orderCol . ' ' . $orderDirn));

        return $query;
    }
}

Обрати внимание: мы не делаем SELECT *. Только те поля, которые реально нужны в списке. Это даёт выигрыш уже на этапе выборки.

Контроллер корзины (фронтенд)

Пример логики добавления в корзину. Сессии и оплату добавим позже, но структура должна быть такой.

<?php
namespace MyCompany\Component\Shop\Site\Controller;

use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\CMS\Factory;
use Joomla\CMS\Response\JsonResponse;
use Joomla\CMS\Session\Session;

class CartController extends BaseController
{
    public function add()
    {
        // Проверка токена (CSRF) — обязательно!
        Session::checkToken() or jexit('Invalid Token');

        $app = Factory::getApplication();
        $input = $app->getInput();

        $productId = $input->getInt('id', 0);
        $qty = $input->getInt('qty', 1);

        // Логика: берём сессию, добавляем товар
        // $session = Factory::getSession();
        // ... логика ...

        // Возвращаем JSON (если это AJAX) или редиректим
        echo new JsonResponse(['status' => 'ok', 'message' => 'Товар добавлен']);
        $app->close();
    }
}
Не забудь: Session::checkToken() — это не опция, а обязательное требование. Без него любая форма с POST становится уязвимой к CSRF.

Почему это будет быстрее

Минимум JOIN'ов

Список товаров — один запрос к одной таблице. Связанные сущности подтягиваются только по требованию.

Только нужные поля

Никаких SELECT *. Выбираем ровно то, что отображается в списке.

Индексы на WHERE

state и catid проиндексированы — фильтрация по ним не сканирует таблицу.

Нет лишнего кэша

Кэшируем явно и точечно, а не «всё подряд на всякий случай».

Для 1000 товаров это даёт разницу в разы: админка открывается мгновенно, фронтенд-каталог не грузит сотни килобайт JSON'а на каждый запрос.

Что дальше

Это скелет. Дальше — по шагам:

  1. Добавить форму редактирования товара (ProductModel + ProductTable).
  2. Сделать таблицу заказов и позиций заказа.
  3. Реализовать корзину на сессии (или в БД для авторизованных).
  4. Прикрутить AJAX-обновление корзины без перезагрузки.
  5. Экспорт/импорт CSV для массовой загрузки товаров.
  6. Поиск с индексами (FULLTEXT или отдельный поисковый индекс).

Если хочешь — в следующей статье разберём форму редактирования товара и Table-класс, а потом и корзину с оформлением заказа. Пиши в комментариях, что интереснее в первую очередь. Ах да, комментарии отключил, никто туда не пишет ) так что пиши на почту. адрес найдешь в разделе "о блоге"

Сколько токенов и денег съест магазин для Joomla
Расчёт для Joomla 5 и лёгкого компонента «каталог + корзина» без тяжёлых ORM. Модель deepseek-v4.1-flash: $0.15 за 1M входных, $0.003 за 1M кэшированных, $0.60 за 1M выходных токенов. В непиковые часы (вне 12:00–18:00 UTC по будням и все выходные) цена вдвое ниже.
Что делаем Вход / выход Днём Ночью
1. Скелет из статьи
каталог, корзина в сессии, заказ в БД, список товаров в админке
4M / 120k $0.29 $0.14
2. Рабочий магазин
категории, карточки, оформление заказа, статусы в админке, импорт CSV, вёрстка под шаблон
15M / 500k $1.12 $0.56
3. Полный магазин
оплата, доставка, обмен с 1С, SEO-разметка, тесты и круг правок по замечаниям
50M / 2M $3.92 $1.96
4. То же в 3–4 захода
каждая новая сессия заново читает код и файлы — вход растёт почти вдвое
90M / 3M $6.70 $3.35
1M токенов ≈ 1200 страниц текста
Модель поумнее (deepseek-v4-pro, $1.32 / $3.96) — умножай на 8–9
Точность оценки ±50%
Суть: Чем работа с нейросетями отличается от обычного программирования вручную
С нейросетями теперь можно сделать всё что угодно — если много денег и долго. Но искусство — это попасть в идеальный результат с первого промпта. Потому что в разработке с ИИ платят не за строчки кода, а за переотправленный контекст: каждая правка, каждый «а теперь поправь отступ», каждая новая сессия заново тащат на вход весь код и историю.
Отсюда два разных ремесла. «Долго и дорого» — это когда вы правите по кругу и платите за каждую попытку; так тоже можно, но это не работа, а перебор. «С первого захода» — когда вы заранее дали схему БД, список файлов, соглашения и полное ТЗ на этап, и модель написала близко к нужному сразу. Тогда строка 4 таблицы просто не наступает.
Экономия — не в дешёвой модели, а в качестве первого промпта и непрерывности сессии: не рвать контекст без нужды, не дробить на микро-правки, разделять этапы осознанно (скелет → рабочий магазин → полный). Это и есть «искусство» — сделать идеально с первого раза, а не героически переделывать.
«Вход / выход» — токены на чтение контекста и на написание кода; «днём» и «ночью» — цена в долларах по дневному и непиковому тарифу. Как считалось: 65% входа приходит из кэша (агент переотправляет тот же контекст, кэш стоит $0.003 вместо $0.15). Итог = вход × (0.35 × 0.15 + 0.65 × 0.003) / 1M + выход × 0.60 / 1M, для непикового тарифа делится на 2.
Важно: цифры — стоимость написания кода. Тестирование на живом Joomla, приёмка и правки по замечаниям добавляют 30–60% сверху, а приём платежей упирается не в токены, а в договор с эквайером.



Категории:

Категории

← Назад к списку

Посетителей сегодня: 0
о блоге | карта блога | 📡 Подписаться на RSS

© Digital Specialist | Не являемся сотрудниками Google, Яндекса и NASA